local re = re
local sdk = sdk
local d2d = d2d
local imgui = imgui
local log = log
local json = json
local draw = draw
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 thread = thread

local Core = require("_CatLib")
local Draw = require("_CatLib.draw")
local CONST = require("_CatLib.const")

local mod = require("mhwilds_overlay.mod")
local OverlayData = require("mhwilds_overlay.data")
local DpsChartData = require("mhwilds_overlay.dps_chart.data")
local BossData = require("mhwilds_overlay.boss.data")
local StatusData = require("mhwilds_overlay.status.data")

local CollectorDamage = require("mhwilds_overlay.collector_damage")
local CollectorPlayer = require("mhwilds_overlay.collector_player")

local MAX_UPDATES_PER_TICK = 4
local function NewTrackData()
    return {
        TickIndex = 0,
        KnownContexts = {},
        KnownCount = 0,

        UpdatedCount = 0, -- 当前帧已更新的数量
        UpdatesPerTick = MAX_UPDATES_PER_TICK,
        UpdatedContexts = {},
    }
end

local TrackData = NewTrackData()

local function UpdateTrackData()
    TrackData.TickIndex = TrackData.TickIndex + 1
    TrackData.UpdatedCount = 0

    if TrackData.TickIndex > TrackData.KnownCount*TrackData.UpdatesPerTick or TrackData.UpdatedCount > TrackData.KnownCount then
        TrackData = NewTrackData()
    end
end

local function ClearData()
    TrackData = NewTrackData()
end

mod.OnDebugFrame(function ()
    -- imgui.push_font(Core.LoadImguiCJKFont())
    -- for ctx, data in pairs(OverlayData.EnemyInfo) do
    --     local id = ctx:get_EmID()
    --     imgui.text(string.format("%s: %0.1f/%0.1f", Core.GetEnemyName(id), data.HP, data.MaxHP))
    -- end
    -- imgui.pop_font()
    for hunter, data in pairs(OverlayData.HunterInfo) do
        if not hunter.IsLeave and hunter.get_IsCombatBoss then
            imgui.text("Hunter is combat boss: " .. tostring(hunter:get_IsCombatBoss()))
            imgui.text("Hunter is player: " .. tostring(data.IsPlayer))
            imgui.text("Hunter IsCombatBoss: " .. tostring(data.IsCombatBoss))
            imgui.text("Hunter FightingTime: " .. tostring(data.FightingTime))
            imgui.text("Hunter LastCombatBossStartTime: " .. tostring(data.LastCombatBossStartTime))
        end
    end
end)

local DeltaTime = 0.016 -- 时间停止更新时，假设每帧16ms
mod.OnUpdateBehavior(function ()
    UpdateTrackData()

    if OverlayData.QuestStats.ElapsedTime == nil then
        OverlayData.QuestStats.ElapsedTime = 0
        OverlayData.QuestStats.LimitTime = -1
    end
    local isPlayingQuest = Core.IsPlayingQuest()
    if Core.IsActiveQuest() then
        if isPlayingQuest then
            OverlayData.QuestStats.ElapsedTime = Core.GetQuestElapsedTime()
            if OverlayData.QuestStats.LimitTime <= 0 then
                OverlayData.QuestStats.LimitTime = Core.GetQuestTimeLimit()
            end

            for hunter, data in pairs(OverlayData.HunterInfo) do
                if not data or data.IsLeave then
                    goto continue
                end

                if data.IsPlayer then
                    if not data.Name or not data.HR then
                        OverlayData.HunterInfo[hunter].Name = OverlayData.GetHunterName(hunter)
                        OverlayData.HunterInfo[hunter].HR = OverlayData.GetHunterHR(hunter)
                    end
                else
                    if not data.Name then
                        OverlayData.HunterInfo[hunter].Name = OverlayData.GetOtomoName(hunter)
                    end
                end

                if data.IsPlayer and OverlayData.HunterDamageRecords[hunter] and hunter.get_IsCombatBoss then
                    local isCombat = false
                    local ok = pcall(function ()
                        isCombat = hunter:get_IsCombatBoss()
                    end)
                    if not ok then
                        isCombat = false
                        data.IsLeave = true
                    end

                    if isCombat and OverlayData.HunterInfo[hunter].LastCombatBossStartTime > 0 then
                        local fightTime = OverlayData.QuestStats.ElapsedTime - OverlayData.HunterInfo[hunter].LastCombatBossStartTime
                        OverlayData.HunterInfo[hunter].FightingTime = OverlayData.HunterInfo[hunter].FightingTime + fightTime
                    end
                    OverlayData.HunterInfo[hunter].LastCombatBossStartTime = OverlayData.QuestStats.ElapsedTime
                    OverlayData.HunterInfo[hunter].IsCombatBoss = isCombat
                end
                if data.IsPlayer then
                    local wpType = OverlayData.HunterInfo[hunter].WeaponType
                    local ok = pcall(function ()
                        wpType = hunter:get_Weapon()._WpType
                    end)
                    if ok then
                        OverlayData.HunterInfo[hunter].WeaponType = wpType
                    else
                        data.IsLeave = true
                    end
                end

                ::continue::
            end
        end
    elseif mod.Config.ClearDataAfterQuestComplete then
        OverlayData.QuestStats.ElapsedTime = 0
        OverlayData.QuestStats.LimitTime = -1
    end
    
    if (OverlayData.QuestStats.LimitTime <= 0) or (OverlayData.QuestStats.ElapsedTime - OverlayData.SimulatedTime < 0.001 and not isPlayingQuest) then
        OverlayData.SimulatedTime = OverlayData.SimulatedTime + DeltaTime
    else
        OverlayData.SimulatedTime = OverlayData.QuestStats.ElapsedTime
    end
end)

local Enemy_GetHealthManager = Core.TypeMethod("app.EnemyCharacter", "get_HealthMgr")

local Enemy_ContextHolder = Core.TypeField("app.EnemyCharacter", "_Context")
local EnemyContextHolder_Context = Core.TypeField("app.cEnemyContextHolder", "_Em")

local HealthManager_GetHealth = Core.TypeMethod("app.cHealthManager", "get_Health")
local HealthManager_GetMaxHealth = Core.TypeMethod("app.cHealthManager", "get_MaxHealth")

---@param character app.EnemyCharacter
local function ShouldUpdate(character)
    if not TrackData.KnownContexts[character] then
        TrackData.KnownContexts[character] = true
        TrackData.KnownCount = TrackData.KnownCount + 1
    end

    if OverlayData.EnemyInfo[character] and OverlayData.EnemyInfo[character].IsTarget then
        return true
    end

    local shouldUpdate = false
    if TrackData.UpdatedCount < TrackData.UpdatesPerTick then
        if TrackData.UpdatedContexts[character] then
           return
        end

        TrackData.UpdatedCount = TrackData.UpdatedCount + 1
        TrackData.UpdatedContexts[character] = true
        shouldUpdate = true
    end

    if not shouldUpdate then
        return
    end
end

---@param this app.EnemyCharacter
local function UpdateEnemyHealth(this, force)
    -- 这个其实不太能提高多少性能，因为实际上 get health 貌似是个消耗很低很低的东西（实测约90次调用不到0.6ms->0.2ms）
    -- 反而导致逻辑变复杂+更新变迟缓许多，除非在增加受伤的钩子，在受伤后强制刷新。
    -- 仔细想想说不定不错？不是一直update，而是在受伤之后才 update
    -- if not force and not ShouldUpdate(this) then
    --     return
    -- end

    local ctxHolder = Enemy_ContextHolder:get_data(this)
    if ctxHolder == nil then return end

    local ctx = EnemyContextHolder_Context:get_data(ctxHolder)
    if ctx == nil then return end

    OverlayData.InitEnemyCtx(ctx, this)

    local hpMgr = Enemy_GetHealthManager:call(this)
    if hpMgr == nil then return end

    local hp = HealthManager_GetHealth:call(hpMgr)
    local maxHp = HealthManager_GetMaxHealth:call(hpMgr)

    local info = OverlayData.EnemyInfo[ctx]
    if info then
        if info.IsBoss then
            -- mod.verbose(string.format("Update boss hp: %s/%s", tostring(hp), tostring(maxHp)))
        elseif info.IsZako then
            -- mod.verbose(string.format("Update zako hp: %s/%s", tostring(hp), tostring(maxHp)))
        elseif info.IsAnimal then
            -- mod.verbose(string.format("Update animal hp: %s/%s", tostring(hp), tostring(maxHp)))
        end
    end
    OverlayData.UpdateEnemyCtxHealth(ctx, hp, maxHp)
end

-- evDamage_Health(System.Single) 只在单人有用
mod.HookFunc("app.EnemyCharacter", "evHit_Damage(app.HitInfo)",
function (args)
    -- mod.verbose("Eval Damage hook")
    local this = sdk.to_managed_object(args[2])
    if not this then return end

    local storage = thread.get_hook_storage()
    storage["this"] = this
end, function (retval)
    local storage = thread.get_hook_storage()
    local this = storage["this"]
    if not this then
        return retval
    end

    -- mod.verbose("Eval Damage at " .. tostring(this))
    UpdateEnemyHealth(this, true)
end)

-- mod.OnPreUpdateBehavior(function ()
--     mod.InitCost("EnemyUpdate")
--     mod.InitCost("EnemyUpdateHealth")
-- end)

mod.HookFunc("app.EnemyCharacter", "doUpdateEnd()",
function(args)
    -- 理论上使用 RequestCtx 可能更好，因为即使是 to_managed_object 也会在巨大的调用下消耗大约0.2ms

    -- 不使用 Core.ToEnemyCharacter，因为在这个调用量下这甚至可能导致~0.05ms的性能损失？
    local this = sdk.to_managed_object(args[2])
    if this == nil then return end

    if not OverlayData.IsQuestHost and ShouldUpdate(this) then
        -- mod.verbose("ShouldUpdate " .. tostring(this))
        UpdateEnemyHealth(this, true)
        -- local ctx = OverlayData.GetEnemyContext(this)
        -- if BossData.ComplexDataCollectionTargets[ctx] then
        --     mod.verbose("  Update " .. tostring(ctx))
        --     BossData.UpdateEnemyCtx(OverlayData.EnemyCharacterData[this].Ctx)
        -- end
    end

    -- mod.RecordCost("EnemyUpdate", function ()
        -- OverlayData.RequestUpdateEnemyHealth = true
        if OverlayData.RequestUpdateEnemyHealth or OverlayData.EnemyCharacterData[this] == nil then
            -- mod.verbose("UpdateEnemyCharacter " .. tostring(this))
            UpdateEnemyHealth(this, true)
            OverlayData.RequestUpdateEnemyHealth = false
        end

        if OverlayData.IsQuestHost then
            local ctx = OverlayData.EnemyCharacterData[this].Ctx
            if OverlayData.EnemyInfo[ctx] and OverlayData.EnemyInfo[ctx].HP > 0 then
                BossData.UpdateEnemyCtx(OverlayData.EnemyCharacterData[this].Ctx)
            end
        end
    -- end, true)
    -- mod.RecordCost("EnemyUpdateHealth", function ()
    --     UpdateEnemyHealth(this)
    -- end, true)
end)
