-- AlwaysKickable.lua  v1.5
-- Wall Execution · Force Kick_Strong · Axe Stealth Kill

local CONFIG_FILE = "AlwaysKickable.json"
local cfg = {
    enabled             = true,
    wall_angle          = 180.0,
    force_kick_strong   = false,
    force_axe_stealth   = false,
    axe_target_radius   = 3.0,
    hold_to_activate    = false,
    kick_cooldown       = 0.0,
    hold_extend         = 5,
    hud_x               = 20,
    hud_y               = 50,
    hud_font_size       = 20,
}

local function ak_log(msg) log.info("[AlwaysKickable] " .. tostring(msg)) end

local _cooldown_until = 0.0
local function is_on_cooldown()
    return cfg.kick_cooldown > 0 and os.clock() < _cooldown_until
end
local function start_cooldown()
    if cfg.kick_cooldown > 0 then
        _cooldown_until = os.clock() + cfg.kick_cooldown
    end
end

local function save_config() json.dump_file(CONFIG_FILE, cfg) end
local function load_config()
    local d = json.load_file(CONFIG_FILE)
    if d then for k, v in pairs(d) do cfg[k] = v end end
end
load_config()

local hooks_installed = false
local _axe_probe_seen = {}
local _axe_common_check_stack = {}
local _axe_deny_stagger_stack = {}
local _axe_bb_check_stack = {}

local function safe_call(obj, m, ...)
    if not obj then return nil end
    local ok, v = pcall(obj.call, obj, m, ...)
    return ok and v or nil
end

local function try_fields(obj, names)
    if not obj then return nil end
    for _, n in ipairs(names) do
        local ok, v = pcall(obj.get_field, obj, n)
        if ok and v ~= nil then return v end
    end
    return nil
end

local function safe_set(obj, field_name, value)
    if not obj then return false end
    local ok = pcall(obj.get_field, obj, field_name)
    if not ok then return false end
    ok = pcall(obj.set_field, obj, field_name, value)
    return ok
end

local function get_type_name(obj)
    if not obj then return "nil" end

    local type_def = nil
    pcall(function()
        type_def = obj:get_type_definition()
    end)
    if not type_def then return "unknown" end

    local full_name = nil
    pcall(function()
        full_name = type_def:get_full_name()
    end)
    return full_name or "unknown"
end

local function log_axe_probe(driver, bb, ctx)
    local key = table.concat({
        get_type_name(driver),
        get_type_name(bb),
        get_type_name(ctx),
    }, " | ")

    if _axe_probe_seen[key] then return end
    _axe_probe_seen[key] = true
    ak_log("axe probe: " .. key)
end

local function push_hook_state(stack, value)
    stack[#stack + 1] = value
end

local function pop_hook_state(stack, default)
    local index = #stack
    if index == 0 then return default end
    local value = stack[index]
    stack[index] = nil
    return value
end

local _hook_allow = true
local _axe_target_ctx = nil
local _axe_target_key = nil
local _axe_target_scan_tick = 0
local _axe_bb_types = false
local is_alive_target
local clear_axe_interact_flags
local release_axe_target_lock

local function get_char_mgr()
    return sdk.get_managed_singleton("app.CharacterManager")
end

local function get_player_context()
    local cm = get_char_mgr()
    if not cm then return nil end
    return try_fields(cm, { "<PlayerContextFast>k__BackingField", "_PlayerContextFast_k__BackingField" })
end

local function get_player_pos()
    local pc = get_player_context()
    if not pc then return nil end
    local pos = try_fields(pc, { "_PositionFast_k__BackingField", "<PositionFast>k__BackingField" })
    if pos then return pos end
    local tf = try_fields(pc, { "<Transform>k__BackingField", "_Transform_k__BackingField" })
    return tf and safe_call(tf, "get_Position")
end

local function dist_sq(a, b)
    if not a or not b then return math.huge end
    local dx = (a.x or 0) - (b.x or 0)
    local dy = (a.y or 0) - (b.y or 0)
    local dz = (a.z or 0) - (b.z or 0)
    return dx * dx + dy * dy + dz * dz
end

local function get_game_object_from_ctx(ctx)
    if not ctx then return nil end
    local tf = try_fields(ctx, { "<Transform>k__BackingField", "_Transform_k__BackingField" })
    return tf and safe_call(tf, "get_GameObject")
end

local function get_enemy_pos(ctx)
    local tf = try_fields(ctx, { "<Transform>k__BackingField", "_Transform_k__BackingField" })
    return tf and safe_call(tf, "get_Position")
end

local function get_aim_target_go()
    local pc = get_player_context()
    if not pc then return nil end
    local updater = try_fields(pc, { "<CharacterUpdaterBase>k__BackingField", "_CharacterUpdaterBase_k__BackingField" })
    if not updater then return nil end
    local drv = try_fields(updater, { "_LockOnTargetDriver_k__BackingField", "<LockOnTargetDriver>k__BackingField" })
    if not drv then return nil end
    local lockon = try_fields(drv, { "_LockOnTarget", "<LockOnTarget>k__BackingField" })
    if not lockon then return nil end
    local list = try_fields(lockon, { "_LockOnTargetList", "<LockOnTargetList>k__BackingField" })
    if not list then return nil end
    local count = safe_call(list, "get_Count") or 0
    if count <= 0 then return nil end
    local work = nil
    pcall(function() work = list:call("get_Item", 0) end)
    if not work then return nil end
    return try_fields(work, { "_OwnerObject_k__BackingField", "<OwnerObject>k__BackingField" })
end

local function get_axe_bb_types()
    if _axe_bb_types ~= false then return _axe_bb_types end
    _axe_bb_types = {}
    for _, type_name in ipairs({
        "app.StealthKillGrappleInteractHandlingBlckboardUnitBase",
        "app.AxeStealthKillGrappleInteractHandlingBlckboardUnit",
        "app.Cp_B000AxeStealthKillGrappleInteractHandlingBlckboardUnit",
        "app.Cp_B600AxeStealthKillGrappleInteractHandlingBlckboardUnit",
    }) do
        local ok, t = pcall(sdk.typeof, type_name)
        if ok and t then
            _axe_bb_types[#_axe_bb_types + 1] = t
        end
    end
    return _axe_bb_types
end

local function get_axe_stealth_bb(ctx)
    if not ctx then return nil end
    local go = get_game_object_from_ctx(ctx)
    if go then
        for _, t in ipairs(get_axe_bb_types()) do
            local comp = nil
            pcall(function()
                comp = go:call("getComponent(System.Type)", t)
            end)
            if comp then return comp end
        end
    end
    return try_fields(ctx, {
        "<AxeStealthKillGrappleInteractHandlingBlckboardUnit>k__BackingField",
        "_AxeStealthKillGrappleInteractHandlingBlckboardUnit_k__BackingField",
    })
end

local function is_selected_axe_target(ctx)
    if not ctx then return false end
    return is_alive_target(ctx)
end

release_axe_target_lock = function()
    if _axe_target_ctx then
        clear_axe_interact_flags(get_axe_stealth_bb(_axe_target_ctx))
    end
    _axe_target_ctx = nil
    _axe_target_key = nil
    _axe_target_scan_tick = 0
end

local function maintain_axe_target()
    if not cfg.enabled or not cfg.force_axe_stealth then
        release_axe_target_lock()
        return
    end

    local cm = get_char_mgr()
    local list = cm and safe_call(cm, "get_EnemyContextList")
    local count = list and safe_call(list, "get_Count") or 0
    local p_pos = get_player_pos()
    if count <= 0 or not p_pos then
        release_axe_target_lock()
        return
    end

    local aim_go = get_aim_target_go()
    local aim_key = aim_go and tostring(aim_go) or nil
    local max_d2 = (cfg.axe_target_radius or 3.0) * (cfg.axe_target_radius or 3.0)
    local best_ctx = nil
    local best_d2 = max_d2 + 1

    for i = 0, count - 1 do
        local ctx = nil
        pcall(function() ctx = list:call("get_Item", i) end)
        if ctx and is_alive_target(ctx) and get_axe_stealth_bb(ctx) then
            local e_pos = get_enemy_pos(ctx)
            if e_pos then
                local d2 = dist_sq(p_pos, e_pos)
                if d2 <= max_d2 then
                    local is_aimed = false
                    if aim_key then
                        local e_go = get_game_object_from_ctx(ctx)
                        is_aimed = e_go and tostring(e_go) == aim_key or false
                    end
                    if is_aimed then
                        best_ctx = ctx
                        best_d2 = -1
                    elseif best_d2 > 0 and d2 < best_d2 then
                        best_ctx = ctx
                        best_d2 = d2
                    end
                end
            end
        end
    end

    if _axe_target_ctx and (not best_ctx or tostring(_axe_target_ctx) ~= tostring(best_ctx)) then
        clear_axe_interact_flags(get_axe_stealth_bb(_axe_target_ctx))
    end

    _axe_target_ctx = best_ctx
    _axe_target_key = best_ctx and tostring(best_ctx) or nil
end

local function get_context_from_driver(driver)
    if not driver then return nil end
    local updater = safe_call(driver, "get_Updater")
    if not updater then return nil end
    return try_fields(updater, { "<Context>k__BackingField", "_Context_k__BackingField" })
end

local function get_current_hp(ctx)
    if not ctx then return nil end
    local hit_point = safe_call(ctx, "get_HitPoint")
    if not hit_point then return nil end
    return safe_call(hit_point, "get_CurrentHitPoint")
end

is_alive_target = function(ctx)
    local current_hp = get_current_hp(ctx)
    return current_hp ~= nil and current_hp > 0
end

clear_axe_interact_flags = function(bb)
    if not bb then return end
    pcall(function() bb:write_byte(0x9d, 0) end)
    pcall(function() bb:write_byte(0x9e, 0) end)
    pcall(function() bb:write_byte(0x9b, 0) end)
    pcall(function() bb:write_byte(0xb8, 0) end)
end

local function arm_lethal_hp(ctx, hp_value)
    if not ctx then return end
    hp_value = hp_value or 1

    local current_hp = get_current_hp(ctx)
    if current_hp == nil or current_hp <= 0 then return end

    local hit_point = safe_call(ctx, "get_HitPoint")
    if not hit_point then return end

    pcall(function()
        safe_call(hit_point, "set_CurrentHitPoint", hp_value)
    end)
end

local function process_driver(driver)
    if not driver then return end
    pcall(function()
        local updater = safe_call(driver, "get_Updater")
        if updater then
            local ks = try_fields(updater, { "<KickedStateUnit>k__BackingField" })
            if ks then ks:call("requestKickedReservation") end
        end
    end)
end

local function install_hooks()
    if hooks_installed then return end
    hooks_installed = true

    for _, cls in ipairs({
        "app.EnemyInteractHandlingDriver",
        "app.Cp_B600InteractHandlingDriver",
        "app.Cp_C600InteractHandlingDriverBase",
    }) do
        pcall(function()
            local td = sdk.find_type_definition(cls)
            if not td then return end
            local m = td:get_method("canPlayerKickCommonCheck")
            if not m then return end
            sdk.hook(m,
                function(args)
                    if not cfg.enabled then return end
                    if is_on_cooldown() then return end
                    _hook_allow = true
                    process_driver(sdk.to_managed_object(args[2]))
                end,
                function(retval)
                    if not cfg.enabled then return retval end
                    if is_on_cooldown() then return sdk.to_ptr(0) end
                    if not _hook_allow then return sdk.to_ptr(0) end
                    return sdk.to_ptr(1)
                end
            )
            ak_log("hooked: " .. cls .. ".canPlayerKickCommonCheck")
        end)
    end

    pcall(function()
        local td = sdk.find_type_definition("app.EnemyInteractHandlingDriver")
        if not td then return end
        local m = td:get_method("isKickStagger")
        if not m then return end
        sdk.hook(m,
            function(args)
                _hook_allow = true
            end,
            function(retval)
                if not cfg.enabled then return retval end
                return sdk.to_ptr(1)
            end
        )
        ak_log("hooked: isKickStagger")
    end)

    pcall(function()
        local td = sdk.find_type_definition("app.Cp_A000InteractConditionBlackboardUnit")
        if not td then return end
        local m = td:get_method("isEnableStunKick")
        if not m then return end
        sdk.hook(m, function() end,
            function(retval)
                if not cfg.enabled or not cfg.force_kick_strong then return retval end
                return sdk.to_ptr(1)
            end
        )
        ak_log("hooked: isEnableStunKick")
    end)

    pcall(function()
        local td = sdk.find_type_definition("app.Cp_A000InteractConditionBlackboardUnit")
        if not td then return end

        local m1 = td:get_method("get_IsEnableAxeStealthKill")
        if m1 then
            sdk.hook(m1, function() end,
                function(retval)
                    if not cfg.enabled or not cfg.force_axe_stealth then return retval end
                    return sdk.to_ptr(1)
                end
            )
            ak_log("hooked: get_IsEnableAxeStealthKill")
        end

        local m2 = td:get_method("get_IsEnablePullOutAxe")
        if m2 then
            sdk.hook(m2, function() end,
                function(retval)
                    if not cfg.enabled or not cfg.force_axe_stealth then return retval end
                    return sdk.to_ptr(1)
                end
            )
            ak_log("hooked: get_IsEnablePullOutAxe")
        end

        local m3 = td:get_method("isEnableAxeInteract")
        if m3 then
            sdk.hook(m3, function() end,
                function(retval)
                    if not cfg.enabled or not cfg.force_axe_stealth then return retval end
                    return sdk.to_ptr(1)
                end
            )
            ak_log("hooked: isEnableAxeInteract")
        end
    end)

    pcall(function()
        local td = sdk.find_type_definition("app.PlayerBehaviorTreeCondition_CheckStunEnemyLegServer")
        if not td then return end
        local m = td:get_method("evaluate")
        if not m then return end
        sdk.hook(m, function() end,
            function(retval)
                if not cfg.enabled or not cfg.force_kick_strong then return retval end
                return sdk.to_ptr(1)
            end
        )
        ak_log("hooked: CheckStunEnemyLegServer.evaluate")
    end)

    local _ka_pctx = nil
    pcall(function()
        local td = sdk.find_type_definition("app.Cp_B000PlayerKickTargetInteractHandleBlackboardUnit")
        if not td then return end
        local m = td:get_method("setPlayerInteractAction")
        if not m then return end
        sdk.hook(m,
            function(args)
                if cfg.enabled then
                    start_cooldown()
                    if cfg.force_axe_stealth then
                        release_axe_target_lock()
                    end
                end
                if cfg.enabled and cfg.force_kick_strong then
                    _ka_pctx = sdk.to_managed_object(args[3])
                end
            end,
            function(retval)
                if not cfg.enabled or not cfg.force_kick_strong then _ka_pctx = nil; return retval end
                local pctx = _ka_pctx
                _ka_pctx = nil
                if not pctx then return retval end
                pcall(function()
                    local common_ptr = pctx:read_qword(0xf8)
                    if not common_ptr or common_ptr == 0 then return end
                    local common = sdk.to_managed_object(sdk.to_ptr(common_ptr))
                    if not common then return end
                    common:write_qword(0x80, 0x8d3d700000002)
                end)
                return retval
            end
        )
        ak_log("hooked: setPlayerInteractAction → Kick_Strong")
    end)

    -- Axe Stealth Kill
    for _, cls in ipairs({
        "app.EnemyInteractHandlingDriver",
        "app.Cp_B000InteractHandlingDriver",
        "app.Cp_B051InteractHandlingDriver",
        "app.Cp_B053InteractHandlingDriver",
        "app.Cp_B054InteractHandlingDriver",
        "app.Cp_B600InteractHandlingDriver",
        "app.Cp_C600InteractHandlingDriverBase",
        "app.Cp_C510InteractHandlingDriver",
    }) do
        pcall(function()
            local td = sdk.find_type_definition(cls)
            if not td then return end

            local m1 = td:get_method("canStealthKillCommonCheck")
            if m1 then
                sdk.hook(m1,
                    function(args)
                        if not cfg.enabled or not cfg.force_axe_stealth then return end
                        local driver = sdk.to_managed_object(args[2])
                        local ctx = get_context_from_driver(driver)
                        local allow = is_alive_target(ctx) and is_selected_axe_target(ctx)
                        log_axe_probe(driver, nil, ctx)
                        if not allow and _axe_target_key then
                            release_axe_target_lock()
                        end
                        push_hook_state(_axe_common_check_stack, allow)
                    end,
                    function(retval)
                        if not cfg.enabled or not cfg.force_axe_stealth then return retval end
                        local allow = pop_hook_state(_axe_common_check_stack, true)
                        if not allow then return sdk.to_ptr(0) end
                        return sdk.to_ptr(1)
                    end
                )
            end

            local m2 = td:get_method("isDenyStealthKillStagger")
            if m2 then
                sdk.hook(m2,
                    function(args)
                        if not cfg.enabled or not cfg.force_axe_stealth then return end
                        local driver = sdk.to_managed_object(args[2])
                        local ctx = get_context_from_driver(driver)
                        local allow = is_alive_target(ctx) and is_selected_axe_target(ctx)
                        if not allow and _axe_target_key then
                            release_axe_target_lock()
                        end
                        push_hook_state(_axe_deny_stagger_stack, allow)
                    end,
                    function(retval)
                        if not cfg.enabled or not cfg.force_axe_stealth then return retval end
                        local allow = pop_hook_state(_axe_deny_stagger_stack, true)
                        if not allow then return sdk.to_ptr(1) end
                        return sdk.to_ptr(0)
                    end
                )
            end
        end)
    end

    for _, cls in ipairs({
        "app.StealthKillGrappleInteractHandlingBlckboardUnitBase",
        "app.AxeStealthKillGrappleInteractHandlingBlckboardUnit",
        "app.Cp_B000AxeStealthKillGrappleInteractHandlingBlckboardUnit",
        "app.Cp_B600AxeStealthKillGrappleInteractHandlingBlckboardUnit",
    }) do
        pcall(function()
            local td = sdk.find_type_definition(cls)
            if not td then return end
            local m = td:get_method("checkInteractCondition")
            if not m then return end
            sdk.hook(m,
                function(args)
                    if not cfg.enabled or not cfg.force_axe_stealth then return end
                    pcall(function()
                        local bb = sdk.to_managed_object(args[2])
                        if not bb then return end
                        local ctx = try_fields(bb, { "<Context>k__BackingField", "_Context_k__BackingField" })
                        log_axe_probe(nil, bb, ctx)
                        local allow = is_alive_target(ctx) and is_selected_axe_target(ctx)
                        push_hook_state(_axe_bb_check_stack, allow)
                        if not allow then
                            clear_axe_interact_flags(bb)
                            if _axe_target_key then
                                release_axe_target_lock()
                            end
                            return
                        end

                        pcall(function() bb:write_byte(0x9b, 1) end)
                        pcall(function() bb:write_byte(0x9d, 1) end)
                        pcall(function() bb:write_byte(0x9e, 1) end)
                        pcall(function() bb:write_byte(0xb8, 1) end)
                    end)
                end,
                function(retval)
                    if not cfg.enabled or not cfg.force_axe_stealth then return retval end
                    local allow = pop_hook_state(_axe_bb_check_stack, true)
                    if not allow then return sdk.to_ptr(0) end
                    return retval
                end
            )
            ak_log("hooked: " .. cls .. ".checkInteractCondition")
        end)
    end

    for _, cls in ipairs({
        "app.StealthKillGrappleInteractHandlingBlckboardUnitBase",
        "app.AxeStealthKillGrappleInteractHandlingBlckboardUnit",
        "app.Cp_B000AxeStealthKillGrappleInteractHandlingBlckboardUnit",
        "app.Cp_B600AxeStealthKillGrappleInteractHandlingBlckboardUnit",
    }) do
        pcall(function()
            local td = sdk.find_type_definition(cls)
            if not td then return end
            local m = td:get_method("onProcess")
            if not m then return end
            sdk.hook(m,
                function(args)
                    if not cfg.enabled or not cfg.force_axe_stealth then return end
                    pcall(function()
                        local bb = sdk.to_managed_object(args[2])
                        if not bb then return end
                        local ctx = try_fields(bb, { "<Context>k__BackingField", "_Context_k__BackingField" })
                        if not ctx then return end
                        if not is_alive_target(ctx) or not is_selected_axe_target(ctx) then return end
                        arm_lethal_hp(ctx, 1)
                    end)
                end,
                function(retval)
                    return retval
                end
            )
            ak_log("hooked: " .. cls .. ".onProcess")
        end)
    end

    ak_log("hooked: AxeStealthKill")

    -- Wall Execution: angle patch
    pcall(function()
        local td = sdk.find_type_definition("app.FinisherAttackGrappleConditionBlackboardUnit.Core")
        if not td then return end
        local m = td:get_method("getConditionParameter")
        if not m then return end
        sdk.hook(m, function() end,
            function(retval)
                local param = sdk.to_managed_object(retval)
                if not param then return retval end
                pcall(function()
                    param:write_float(0x20, 999.0)  -- _MaxAnglePlayer
                    param:write_float(0x30, 999.0)  -- _MaxAngleSelf
                end)
                return retval
            end
        )
        ak_log("hooked: getConditionParameter (WallExecution angle patch)")
    end)

    -- checkDeadOnDamageBy
    pcall(function()
        local td = sdk.find_type_definition("app.Cp_B000PlayerKickTargetInteractHandleBlackboardUnit")
        if not td then return end
        local m = td:get_method("checkDeadOnDamageBy")
        if not m then return end
        sdk.hook(m,
            function(args)
                if not cfg.enabled then return end
                local bb = sdk.to_managed_object(args[2])
                if not bb then return end
                pcall(function()
                    local ctx = try_fields(bb, { "<Context>k__BackingField", "_Context_k__BackingField" })
                    if not ctx then return end
                    arm_lethal_hp(ctx, 1)
                end)
            end,
            function(retval)
                if not cfg.enabled then return retval end
                return sdk.to_ptr(1)
            end
        )
        ak_log("hooked: checkDeadOnDamageBy")
    end)

end

install_hooks()

-- Input detection for hold-to-activate mode
local _gp_singleton = sdk.get_native_singleton("via.hid.Gamepad")
local _gp_typedef   = sdk.find_type_definition("via.hid.GamePad")
local _gp_device    = nil
local GP_X          = 0x20020
local KB_F          = 0x46

local _hold_active = false
local _hold_frames = 0

local function is_kick_button_held()
    if reframework:is_key_down(KB_F) then return true end
    if not _gp_device and _gp_singleton and _gp_typedef then
        pcall(function()
            _gp_device = sdk.call_native_func(_gp_singleton, _gp_typedef, "get_MergedDevice")
        end)
    end
    if _gp_device then
        local ok, btn = pcall(_gp_device.call, _gp_device, "get_Button")
        if ok and btn and (btn & GP_X) ~= 0 then return true end
    end
    return false
end

-- Detect kick button for hold-to-activate mode
re.on_pre_application_entry("UpdateBehavior", function()
    if cfg.hold_to_activate then
        if is_kick_button_held() then
            _hold_frames = cfg.hold_extend
        elseif _hold_frames > 0 then
            _hold_frames = _hold_frames - 1
        end
        cfg.enabled = _hold_frames > 0
    end

    _axe_target_scan_tick = (_axe_target_scan_tick + 1) % 3
    if _axe_target_scan_tick == 0 then
        maintain_axe_target()
    end
end)

re.on_draw_ui(function()
    if imgui.tree_node("AlwaysKickable") then
        local changed, new_val = imgui.checkbox("Always Icon + 1-Click Execute", cfg.enabled)
        if changed then
            cfg.enabled = new_val
            if new_val then cfg.hold_to_activate = false end
            if not new_val then cfg.hold_to_activate = true end
            save_config()
        end
        imgui.same_line()
        imgui.text_colored(cfg.enabled and "● ACTIVE" or "○ DISABLED",
            cfg.enabled and 0xFF00FF00 or 0xFF888888)

        local ha_changed, ha_new = imgui.checkbox("No Icon + Double-Tap Execute", cfg.hold_to_activate)
        if ha_changed then
            cfg.hold_to_activate = ha_new
            if not ha_new then cfg.enabled = true end
            save_config()
        end
        imgui.same_line()
        imgui.text_colored(cfg.hold_to_activate and "● ACTIVE" or "○ DISABLED",
            cfg.hold_to_activate and 0xFF00FF00 or 0xFF888888)

        local he_changed, he_new = imgui.slider_int("No Icon Hold Frames", cfg.hold_extend, 5, 20)
        if he_changed then cfg.hold_extend = he_new; save_config() end
        imgui.text_colored(string.format("  Active for %d frames after releasing button", cfg.hold_extend), 0xFFAAFFFF)

        imgui.spacing()
        local ks_changed, ks_new = imgui.checkbox("Always Kick", cfg.force_kick_strong)
        if ks_changed then cfg.force_kick_strong = ks_new; save_config() end
        imgui.same_line()
        imgui.text_colored(
            cfg.force_kick_strong and "<= ON: Kick" or "<= OFF: Wall Kill only mode",
            cfg.force_kick_strong and 0xFF00FFFF or 0xFF888888)

        local as_changed, as_new = imgui.checkbox("Always Axe Stealth Kill", cfg.force_axe_stealth)
        if as_changed then cfg.force_axe_stealth = as_new; save_config() end
        imgui.same_line()
        imgui.text_colored(
            cfg.force_axe_stealth and "<= ON: Force stealth-axe interaction checks" or "<= OFF: Default stealth-axe condition",
            cfg.force_axe_stealth and 0xFF66DDFF or 0xFF888888)

        local atr_changed, atr_new = imgui.slider_float("Stealth Target Radius", cfg.axe_target_radius, 1.0, 4.0)
        if atr_changed then cfg.axe_target_radius = atr_new; save_config() end
        imgui.text_colored(string.format("  Aim target first, otherwise nearest valid stealth target within %.1fm", cfg.axe_target_radius), 0xFFAAFFFF)

        imgui.spacing()
        local a_changed, a_new = imgui.slider_float("Kick Angle", cfg.wall_angle, 10.0, 360.0)
        if a_changed then cfg.wall_angle = a_new; save_config() end
        imgui.text_colored(string.format("  Current: %.0f deg (default ~45, 180=omnidirectional)", cfg.wall_angle), 0xFFAAFFAA)

        imgui.spacing()
        local cd_changed, cd_new = imgui.slider_float("Kick Cooldown (sec)", cfg.kick_cooldown, 0.0, 30.0)
        if cd_changed then cfg.kick_cooldown = cd_new; save_config() end
        if is_on_cooldown() then
            imgui.text_colored(string.format("  [Cooldown: %.1fs remaining]", _cooldown_until - os.clock()), 0xFFFF4444)
        else
            imgui.text_colored(
                cfg.kick_cooldown > 0 and string.format("  Cooldown %.1fs (0 = OFF)", cfg.kick_cooldown) or "  Cooldown: OFF",
                cfg.kick_cooldown > 0 and 0xFFFFAA00 or 0xFF888888)
        end

        imgui.spacing()
        imgui.text("HUD Settings:")
        local hx_c, hx_n = imgui.slider_int("HUD X", cfg.hud_x, 0, 3840)
        if hx_c then cfg.hud_x = hx_n; save_config() end
        local hy_c, hy_n = imgui.slider_int("HUD Y", cfg.hud_y, 0, 2160)
        if hy_c then cfg.hud_y = hy_n; save_config() end
        local hf_c, hf_n = imgui.slider_int("HUD Font Size", cfg.hud_font_size, 10, 80)
        if hf_c then cfg.hud_font_size = hf_n; _hud_font = nil; save_config() end

        imgui.tree_pop()
    end
end)

-- ══ HUD: Cooldown Display ══

local _hud_font = nil
local _hud_font_size = 0

local function draw_cooldown_hud()
    if not cfg.enabled or cfg.kick_cooldown <= 0 then return end

    if not _hud_font or _hud_font_size ~= cfg.hud_font_size then
        _hud_font = d2d.Font.new("Tahoma", cfg.hud_font_size, true)
        _hud_font_size = cfg.hud_font_size
    end

    local remaining = _cooldown_until - os.clock()
    local msg, color
    if remaining > 0 then
        local ratio = remaining / cfg.kick_cooldown
        msg = string.format("KICK  %.1fs", remaining)
        color = ratio > 0.5 and 0xFFFF4444 or (ratio > 0.2 and 0xFFFFAA00 or 0xFF44FF44)
    else
        msg = "KICK  READY"
        color = 0xFF44FF44
    end

    d2d.text(_hud_font, msg, cfg.hud_x, cfg.hud_y, color)
end

d2d.register(function()
    _hud_font = d2d.Font.new("Tahoma", cfg.hud_font_size, true)
    _hud_font_size = cfg.hud_font_size
end, draw_cooldown_hud)
