local Logger = require("libraries/logger")

-- Hero control system for RPC commands
if HeroController == nil then
    HeroController = class({})
end

function HeroController:Init()
    Logger.info("HERO_CONTROLLER", "Initializing Hero Controller")
    
    -- Store hero references
    self.heroes = {}
    self.command_queue = {}
    self.processing = false
    
    -- Start command processing
    Timers:CreateTimer(0.1, function()
        self:ProcessCommands()
        return 0.1  -- Process every 100ms
    end)
end

function HeroController:RegisterHero(hero)
    if not hero or not IsValidEntity(hero) then return end
    
    local playerID = hero:GetPlayerID()
    self.heroes[playerID] = hero
    
    Logger.info("HERO_CONTROLLER", "Registered hero for player " .. playerID .. ": " .. hero:GetUnitName())
end

function HeroController:GetHero(playerID)
    return self.heroes[playerID]
end

function HeroController:ProcessCommands()
    if self.processing or #self.command_queue == 0 then
        return
    end
    
    self.processing = true
    
    -- Process up to 10 commands per tick
    local commands_to_process = math.min(10, #self.command_queue)
    local processed = {}
    local order = {}
    local failures = {}
    local total_processed = 0

    for _ = 1, commands_to_process do
        local command = table.remove(self.command_queue, 1)
        if command then
            local command_type = command.type or "unknown"
            if processed[command_type] == nil then
                processed[command_type] = 0
                table.insert(order, command_type)
            end

            local ok = self:ExecuteCommand(command)
            processed[command_type] = processed[command_type] + 1
            total_processed = total_processed + 1

            if not ok then
                failures[command_type] = (failures[command_type] or 0) + 1
            end
        end
    end

    if total_processed > 0 then
        local MAX_SEGMENTS = 5
        local parts = {}
        local overflow = 0

        for idx, command_type in ipairs(order) do
            local count = processed[command_type]
            if idx <= MAX_SEGMENTS then
                table.insert(parts, string.format("%s x%d", command_type, count))
            else
                overflow = overflow + count
            end
        end

        if overflow > 0 then
            table.insert(parts, string.format("+%d more", overflow))
        end

        local summary = string.format(
            "Processed %d command%s",
            total_processed,
            total_processed == 1 and "" or "s"
        )

        if #parts > 0 then
            summary = summary .. " (" .. table.concat(parts, ", ") .. ")"
        end

        Logger.info("HERO_CONTROLLER", summary)

        if next(failures) then
            local failure_parts = {}
            for command_type, count in pairs(failures) do
                table.insert(failure_parts, string.format("%s x%d", command_type, count))
            end
            table.sort(failure_parts)
            Logger.warn("HERO_CONTROLLER", "Failed commands: " .. table.concat(failure_parts, ", "))
        end
    end
    
    self.processing = false
end

function HeroController:QueueCommand(command)
    table.insert(self.command_queue, command)
end

function HeroController:ExecuteCommand(command)
    local hero = self:GetHero(command.hero_id or command.player_id)
    
    if not hero or not IsValidEntity(hero) or not hero:IsAlive() then
        Logger.warn("HERO_CONTROLLER", "Invalid or dead hero for command: " .. tostring(command.type))
        return false
    end

    -- Execute based on command type
    if command.type == "move" then
        self:MoveHero(hero, command.data)
    elseif command.type == "attack" then
        self:AttackTarget(hero, command.data)
    elseif command.type == "cast_ability" then
        self:CastAbility(hero, command.data)
    elseif command.type == "use_item" then
        self:UseItem(hero, command.data)
    elseif command.type == "stop" then
        self:StopHero(hero)
    elseif command.type == "hold" then
        self:HoldPosition(hero)
    elseif command.type == "patrol" then
        self:PatrolTo(hero, command.data)
    elseif command.type == "level_up" then
        self:LevelUpAbility(hero, command.data)
    elseif command.type == "purchase_item" then
        self:PurchaseItem(hero, command.data)
    elseif command.type == "move_to_target" then
        self:MoveToTarget(hero, command.data)
    elseif command.type == "follow" then
        self:FollowUnit(hero, command.data)
    else
        Logger.warn("HERO_CONTROLLER", "Unknown command type: " .. tostring(command.type))
        return false
    end
    
    return true
end

-- Movement Commands
function HeroController:MoveHero(hero, data)
    if not data.position then return false end
    
    local pos = Vector(data.position.x, data.position.y, data.position.z or 0)
    
    ExecuteOrderFromTable({
        UnitIndex = hero:entindex(),
        OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
        Position = pos,
        Queue = data.queue or false
    })
    return true
end

function HeroController:MoveToTarget(hero, data)
    if not data.target_id then return false end
    
    local target = EntIndexToHScript(data.target_id)
    if not target or not IsValidEntity(target) then return false end
    
    ExecuteOrderFromTable({
        UnitIndex = hero:entindex(),
        OrderType = DOTA_UNIT_ORDER_MOVE_TO_TARGET,
        TargetIndex = target:entindex(),
        Queue = data.queue or false
    })
    return true
end

function HeroController:PatrolTo(hero, data)
    if not data.position then return false end
    
    local pos = Vector(data.position.x, data.position.y, data.position.z or 0)
    
    ExecuteOrderFromTable({
        UnitIndex = hero:entindex(),
        OrderType = DOTA_UNIT_ORDER_PATROL_TO_POSITION,
        Position = pos,
        Queue = data.queue or false
    })
    
    return true
end

-- Combat Commands
function HeroController:AttackTarget(hero, data)
    if not data.target_id then return false end
    
    local target = EntIndexToHScript(data.target_id)
    if not target or not IsValidEntity(target) then 
        -- If specific target not found, try finding nearest enemy
        target = self:FindNearestEnemy(hero, data.range or 1000)
        if not target then return false end
    end
    
    ExecuteOrderFromTable({
        UnitIndex = hero:entindex(),
        OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
        TargetIndex = target:entindex(),
        Queue = data.queue or false
    })
    return true
end

function HeroController:StopHero(hero)
    ExecuteOrderFromTable({
        UnitIndex = hero:entindex(),
        OrderType = DOTA_UNIT_ORDER_STOP,
        Queue = false
    })
    return true
end

function HeroController:HoldPosition(hero)
    ExecuteOrderFromTable({
        UnitIndex = hero:entindex(),
        OrderType = DOTA_UNIT_ORDER_HOLD_POSITION,
        Queue = false
    })
    return true
end

-- Ability Commands
function HeroController:CastAbility(hero, data)
    local ability = nil
    
    -- Find ability by index or name
    if data.ability_index then
        ability = hero:GetAbilityByIndex(data.ability_index)
    elseif data.ability_name then
        ability = hero:FindAbilityByName(data.ability_name)
    end
    
    if not ability or not ability:IsFullyCastable() then
        Logger.warn("HERO_CONTROLLER", "Ability not castable")
        return false
    end
    
    -- Determine cast type
    local behavior = ability:GetBehavior()
    
    if bit.band(behavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET) ~= 0 then
        -- No target ability
        ExecuteOrderFromTable({
            UnitIndex = hero:entindex(),
            OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
            AbilityIndex = ability:entindex(),
            Queue = data.queue or false
        })
        
    elseif bit.band(behavior, DOTA_ABILITY_BEHAVIOR_POINT) ~= 0 then
        -- Point target ability
        if not data.position then return false end
        
        local pos = Vector(data.position.x, data.position.y, data.position.z or 0)
        
        ExecuteOrderFromTable({
            UnitIndex = hero:entindex(),
            OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
            AbilityIndex = ability:entindex(),
            Position = pos,
            Queue = data.queue or false
        })
        
    elseif bit.band(behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET) ~= 0 then
        -- Unit target ability
        if not data.target_id then 
            -- Auto-target nearest enemy if no target specified
            local target = self:FindNearestEnemy(hero, ability:GetCastRange(hero:GetAbsOrigin(), nil))
            if not target then return false end
            data.target_id = target:entindex()
        end
        
        local target = EntIndexToHScript(data.target_id)
        if not target or not IsValidEntity(target) then return false end
        
        ExecuteOrderFromTable({
            UnitIndex = hero:entindex(),
            OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
            AbilityIndex = ability:entindex(),
            TargetIndex = target:entindex(),
            Queue = data.queue or false
        })
    else
        Logger.warn("HERO_CONTROLLER", "Unknown ability behavior")
        return false
    end
    return true
end

function HeroController:LevelUpAbility(hero, data)
    local ability = nil
    
    if data.ability_index then
        ability = hero:GetAbilityByIndex(data.ability_index)
    elseif data.ability_name then
        ability = hero:FindAbilityByName(data.ability_name)
    end
    
    if not ability then return false end
    
    local abilityPoints = hero:GetAbilityPoints()
    if abilityPoints > 0 and ability:GetLevel() < ability:GetMaxLevel() then
        hero:UpgradeAbility(ability)
        Logger.info("HERO_CONTROLLER", "Leveled up ability: " .. ability:GetAbilityName())
        return true
    end
    
    return false
end

-- Item Commands
function HeroController:UseItem(hero, data)
    local item = nil
    
    -- Find item by slot or name
    if data.item_slot then
        item = hero:GetItemInSlot(data.item_slot)
    elseif data.item_name then
        for i = 0, 8 do
            local slot_item = hero:GetItemInSlot(i)
            if slot_item and slot_item:GetAbilityName() == data.item_name then
                item = slot_item
                break
            end
        end
    end
    
    if not item or not item:IsFullyCastable() then
        Logger.warn("HERO_CONTROLLER", "Item not usable")
        return false
    end
    
    -- Determine item cast type
    local behavior = item:GetBehavior()
    
    if bit.band(behavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET) ~= 0 then
        ExecuteOrderFromTable({
            UnitIndex = hero:entindex(),
            OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
            AbilityIndex = item:entindex(),
            Queue = data.queue or false
        })
    elseif bit.band(behavior, DOTA_ABILITY_BEHAVIOR_POINT) ~= 0 then
        if not data.position then return false end
        
        local pos = Vector(data.position.x, data.position.y, data.position.z or 0)
        
        ExecuteOrderFromTable({
            UnitIndex = hero:entindex(),
            OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
            AbilityIndex = item:entindex(),
            Position = pos,
            Queue = data.queue or false
        })
    elseif bit.band(behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET) ~= 0 then
        if not data.target_id then return false end
        
        local target = EntIndexToHScript(data.target_id)
        if not target or not IsValidEntity(target) then return false end
        
        ExecuteOrderFromTable({
            UnitIndex = hero:entindex(),
            OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
            AbilityIndex = item:entindex(),
            TargetIndex = target:entindex(),
            Queue = data.queue or false
        })
    end
    
    Logger.info("HERO_CONTROLLER", "Used item: " .. item:GetAbilityName())
    return true
end

function HeroController:PurchaseItem(hero, data)
    if not data.item_name then return false end
    
    local gold = hero:GetGold()
    local itemCost = GetItemCost(data.item_name)
    
    if gold >= itemCost then
        hero:ModifyGold(-itemCost, false, DOTA_ModifyGold_PurchaseItem)
        local item = CreateItem(data.item_name, hero, hero)
        hero:AddItem(item)
        
        Logger.info("HERO_CONTROLLER", "Purchased item: " .. data.item_name)
        return true
    else
        Logger.warn("HERO_CONTROLLER", "Not enough gold for: " .. data.item_name)
        return false
    end
end

-- Helper Functions
function HeroController:FindNearestEnemy(hero, range)
    local heroPos = hero:GetAbsOrigin()
    local team = hero:GetTeamNumber()
    
    local enemies = FindUnitsInRadius(
        team,
        heroPos,
        nil,
        range,
        DOTA_UNIT_TARGET_TEAM_ENEMY,
        DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
        DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE + DOTA_UNIT_TARGET_FLAG_NO_INVIS,
        FIND_CLOSEST,
        false
    )
    
    if #enemies > 0 then
        return enemies[1]
    end
    
    return nil
end

function HeroController:FollowUnit(hero, data)
    if not data.target_id then return false end
    
    local target = EntIndexToHScript(data.target_id)
    if not target or not IsValidEntity(target) then return false end
    
    -- Create a timer to continuously follow
    local timerName = "follow_" .. hero:entindex()
    
    -- Clear existing follow timer
    if self.followTimers and self.followTimers[timerName] then
        Timers:RemoveTimer(self.followTimers[timerName])
    end
    
    self.followTimers = self.followTimers or {}
    
    self.followTimers[timerName] = Timers:CreateTimer(0.5, function()
        if not IsValidEntity(hero) or not hero:IsAlive() or not IsValidEntity(target) then
            return nil -- Stop following
        end
        
        local distance = (hero:GetAbsOrigin() - target:GetAbsOrigin()):Length2D()
        
        if distance > 300 then
            ExecuteOrderFromTable({
                UnitIndex = hero:entindex(),
                OrderType = DOTA_UNIT_ORDER_MOVE_TO_TARGET,
                TargetIndex = target:entindex(),
                Queue = false
            })
        end
        
        return 0.5 -- Continue following
    end)
    
    return true
end

-- Get hero state for RPC responses
function HeroController:GetHeroState(hero)
    if not hero or not IsValidEntity(hero) then return {} end
    
    return {
        entity_id = hero:entindex(),
        player_id = hero:GetPlayerID(),
        name = hero:GetUnitName(),
        level = hero:GetLevel(),
        position = {
            x = hero:GetAbsOrigin().x,
            y = hero:GetAbsOrigin().y,
            z = hero:GetAbsOrigin().z
        },
        health = hero:GetHealth(),
        max_health = hero:GetMaxHealth(),
        mana = hero:GetMana(),
        max_mana = hero:GetMaxMana(),
        gold = hero:GetGold(),
        alive = hero:IsAlive(),
        abilities = self:GetHeroAbilities(hero),
        items = self:GetHeroItems(hero)
    }
end

function HeroController:GetHeroAbilities(hero)
    local abilities = {}
    
    for i = 0, hero:GetAbilityCount() - 1 do
        local ability = hero:GetAbilityByIndex(i)
        if ability and not ability:IsHidden() then
            table.insert(abilities, {
                index = i,
                name = ability:GetAbilityName(),
                level = ability:GetLevel(),
                cooldown = ability:GetCooldownTimeRemaining(),
                mana_cost = ability:GetManaCost(-1),
                castable = ability:IsFullyCastable()
            })
        end
    end
    
    return abilities
end

function HeroController:GetHeroItems(hero)
    local items = {}
    
    for i = 0, 8 do
        local item = hero:GetItemInSlot(i)
        if item then
            table.insert(items, {
                slot = i,
                name = item:GetAbilityName(),
                charges = item:GetCurrentCharges(),
                cooldown = item:GetCooldownTimeRemaining(),
                castable = item:IsFullyCastable()
            })
        end
    end
    
    return items
end

return HeroController
