-- Debug helpers for headless mode
dbg = {}

-- Stores login state/errors for polling
local loginState = { error = nil }

-- Chat message buffer (circular, max 50 messages)
local chatBuffer = {}
local chatBufferMax = 50

-- Add message to buffer with size limit
local function addToBuffer(msg)
    table.insert(chatBuffer, msg)
    while #chatBuffer > chatBufferMax do
        table.remove(chatBuffer, 1)
    end
end

-- Hook into chat messages when game starts
local function setupChatCapture()
    if g_game and connect then
        connect(g_game, {
            -- Player chat (say, yell, whisper, private, etc.)
            onTalk = function(name, level, mode, text, channelId, pos)
                addToBuffer({
                    type = "talk",
                    name = name,
                    level = level,
                    mode = mode,
                    text = text,
                    channelId = channelId,
                    time = os.time()
                })
            end,
            -- System/server messages (broadcasts, loot, damage, status, etc.)
            onTextMessage = function(mode, text)
                addToBuffer({
                    type = "system",
                    mode = mode,
                    text = text,
                    time = os.time()
                })
            end
        })
    end
end

-- Get recent chat messages
function dbg.chat(count)
    count = count or 10
    local result = {}
    local start = math.max(1, #chatBuffer - count + 1)
    for i = start, #chatBuffer do
        table.insert(result, chatBuffer[i])
    end
    return result
end

-- Clear chat buffer
function dbg.clearChat()
    chatBuffer = {}
end

-- Setup chat capture when entering game
if g_game then
    connect(g_game, {
        onGameStart = setupChatCapture
    })
end

-- Clear any stored error
local function clearError()
    loginState.error = nil
end

-- Store an error for polling
local function setError(msg)
    loginState.error = msg
end

-- Get current client state with context-aware information
function dbg.state()
    if loginState.error then
        local err = loginState.error
        loginState.error = nil -- Clear after reading
        return { state = "error", message = err }
    end

    if g_game.isOnline() then
        local p = g_game.getLocalPlayer()
        if p then
            local pos = p:getPosition()
            return {
                state = "ingame",
                name = p:getName(),
                level = p:getLevel(),
                hp = p:getHealth(),
                maxHp = p:getMaxHealth(),
                mana = p:getMana(),
                maxMana = p:getMaxMana(),
                pos = { x = pos.x, y = pos.y, z = pos.z },
                spectating = g_game.isSpectating(),
                replaying = g_game.isReplaying(),
                canAct = g_game.canPerformGameAction()
            }
        end
        return { state = "ingame" }
    end

    if g_game.isLogging() then
        return { state = "logging_in" }
    end

    -- Check if character list is visible
    if CharacterList and CharacterList.isVisible and CharacterList.isVisible() then
        local chars = {}
        if G and G.characters then
            for _, c in ipairs(G.characters) do
                table.insert(chars, c.characterName or c.name)
            end
        end
        return { state = "character_select", characters = chars }
    end

    return { state = "login_screen" }
end

-- Start account login (async - poll dbg.state() for result)
function dbg.login(account, password)
    clearError()

    if g_game.isOnline() then
        setError("Already logged in")
        return
    end

    -- Get server info from Servers_init
    local host, serverInfo = next(Servers_init or {})
    if not host then
        setError("No server configured")
        return
    end

    local port = serverInfo.port or 7171
    local clientVersion = serverInfo.protocol or 860

    -- Store credentials in G for later use
    G = G or {}
    G.account = account
    G.password = password
    G.host = host
    G.port = port
    G.authenticatorToken = ""
    G.stayLogged = false

    -- Set up protocol
    g_game.setClientVersion(clientVersion)
    g_game.setProtocolVersion(g_game.getClientProtocolVersion(clientVersion))
    g_game.chooseRsa(host)

    -- Create protocol login
    local protocolLogin = ProtocolLogin.create()

    protocolLogin.onLoginError = function(protocol, message, errorCode)
        setError(message or "Login failed")
    end

    protocolLogin.onCharacterList = function(protocol, characters, accountInfo, otui)
        G.characters = characters
        G.characterAccount = accountInfo
        -- Show character list (or just store for dbg.state to report)
        if CharacterList and CharacterList.create then
            CharacterList.create(characters, accountInfo, otui)
            CharacterList.show()
        end
    end

    -- Start login
    protocolLogin:login(host, port, account, password, "", false)
end

-- Enter game with selected character (async - poll dbg.state() for result)
function dbg.enterGame(characterName)
    clearError()

    if not G or not G.characters then
        setError("No character list - call dbg.login() first")
        return
    end

    -- Find character
    local charInfo = nil
    for _, c in ipairs(G.characters) do
        local name = c.characterName or c.name
        if name == characterName then
            charInfo = c
            break
        end
    end

    if not charInfo then
        setError("Character not found: " .. characterName)
        return
    end

    -- Hide character list if visible
    if CharacterList and CharacterList.hide then
        CharacterList.hide()
    end

    -- Enter world
    g_game.loginWorld(
        G.account,
        G.password,
        charInfo.worldName,
        charInfo.worldHost or charInfo.worldIp,
        charInfo.worldPort,
        charInfo.characterName or charInfo.name,
        G.authenticatorToken or "",
        G.sessionKey or "",
        "",  -- recordTo
        G.passToken or ""
    )
end

-- Get player status as a compact string
function dbg.status()
    local p = g_game.getLocalPlayer()
    if not p then return "Not logged in" end
    local pos = p:getPosition()
    return string.format("%s | HP %d/%d | MP %d/%d | %d,%d,%d",
        p:getName(), p:getHealth(), p:getMaxHealth(),
        p:getMana(), p:getMaxMana(), pos.x, pos.y, pos.z)
end

-- Get ASCII map around player
function dbg.map(size)
    size = size or 10
    local p = g_game.getLocalPlayer()
    if not p then return "Not logged in" end

    local pos = p:getPosition()
    local half = math.floor(size / 2)
    local lines = {}

    for dy = -half, half do
        local row = ""
        for dx = -half, half do
            if dx == 0 and dy == 0 then
                row = row .. "@"
            else
                local tile = g_map.getTile({x = pos.x + dx, y = pos.y + dy, z = pos.z})
                if not tile then
                    row = row .. "?"
                elseif tile:getTopCreature() then
                    local c = tile:getTopCreature()
                    if c:isNpc() then row = row .. "N"
                    elseif c:isPlayer() then row = row .. "P"
                    else row = row .. "C" end
                elseif not tile:isWalkable() then
                    row = row .. "#"
                else
                    row = row .. "."
                end
            end
        end
        table.insert(lines, row)
    end
    return table.concat(lines, "\n")
end

-- Get list of nearby creatures
function dbg.creatures()
    local p = g_game.getLocalPlayer()
    if not p then return {} end

    local pos = p:getPosition()
    local result = {}

    -- Try to get spectators from game interface
    local spectators = {}
    if modules and modules.game_interface and modules.game_interface.getMapPanel then
        local mapPanel = modules.game_interface.getMapPanel()
        if mapPanel and mapPanel.getSpectators then
            spectators = mapPanel:getSpectators() or {}
        end
    end

    for _, c in ipairs(spectators) do
        if not c:isLocalPlayer() and c:canBeSeen() then
            local cpos = c:getPosition()
            if cpos.z == pos.z then
                local dist = math.abs(cpos.x - pos.x) + math.abs(cpos.y - pos.y)
                local hp = c:getHealthPercent()
                local hpStr = hp <= 100 and (hp .. "%") or "?"
                local ctype = "monster"
                if c:isNpc() then ctype = "npc"
                elseif c:isPlayer() then ctype = "player" end
                table.insert(result, {
                    name = c:getName(),
                    type = ctype,
                    distance = dist,
                    hp = hpStr
                })
            end
        end
    end

    return result
end

-- Get equipped items
function dbg.inv()
    local p = g_game.getLocalPlayer()
    if not p then return "Not logged in" end

    local slots = {
        [1] = "head",
        [2] = "neck",
        [3] = "back",
        [4] = "body",
        [5] = "right",
        [6] = "left",
        [7] = "legs",
        [8] = "feet",
        [9] = "finger",
        [10] = "ammo"
    }

    local result = {}
    for slot, name in pairs(slots) do
        local item = p:getInventoryItem(slot)
        if item then
            result[name] = {
                id = item:getId(),
                count = item:getCount()
            }
        end
    end
    return result
end

-- Get open container contents
function dbg.bags()
    local result = {}
    local containers = g_game.getContainers()
    for id, container in pairs(containers) do
        local items = {}
        for slot = 0, container:getCapacity() - 1 do
            local item = container:getItem(slot)
            if item then
                table.insert(items, {
                    slot = slot,
                    id = item:getId(),
                    count = item:getCount()
                })
            end
        end
        result[id] = {
            name = container:getName(),
            capacity = container:getCapacity(),
            items = items
        }
    end
    return result
end

-- Get tile info at offset from player
function dbg.tile(dx, dy)
    local p = g_game.getLocalPlayer()
    if not p then return "Not logged in" end

    dx = dx or 0
    dy = dy or 0
    local pos = p:getPosition()
    local tilePos = {x = pos.x + dx, y = pos.y + dy, z = pos.z}
    local tile = g_map.getTile(tilePos)

    if not tile then
        return { pos = tilePos, exists = false }
    end

    local items = {}
    for _, thing in ipairs(tile:getThings() or {}) do
        if thing:isItem() then
            table.insert(items, {
                id = thing:getId(),
                count = thing.getCount and thing:getCount() or 1
            })
        end
    end

    local creature = tile:getTopCreature()
    local creatureInfo = nil
    if creature then
        creatureInfo = {
            name = creature:getName(),
            hp = creature:getHealthPercent()
        }
    end

    return {
        pos = tilePos,
        exists = true,
        walkable = tile:isWalkable(),
        items = items,
        creature = creatureInfo
    }
end

-- Start spectating a player's cast (async - poll dbg.state() for result)
function dbg.spectate(playerName)
    clearError()

    if g_game.isOnline() then
        setError("Already in game - logout first")
        return
    end

    -- Get server info from Servers_init (same pattern as dbg.login)
    local host, serverInfo = next(Servers_init or {})
    if not host then
        setError("No server configured")
        return
    end

    -- Spectating connects to game port (7172), not login port (7171)
    local port = 7172
    local clientVersion = serverInfo.protocol or 860

    -- Set up protocol
    g_game.setClientVersion(clientVersion)
    g_game.setProtocolVersion(g_game.getClientProtocolVersion(clientVersion))
    g_game.chooseRsa(host)

    -- Start spectating
    g_game.spectatePlayer(host, port, playerName)
end

-- Start packet tracing
function dbg.traceStart()
    g_packetTracer.clear()
    g_packetTracer.setEnabled(true)
end

-- Stop packet tracing
function dbg.traceStop()
    g_packetTracer.setEnabled(false)
end

-- Get traced packets (returns list of {timestamp, dir, opcode, size})
function dbg.packets(n)
    local raw = g_packetTracer.getPackets(n or 20)
    local result = {}
    for i, pkt in ipairs(raw) do
        table.insert(result, {
            timestamp = pkt[1],
            dir = pkt[2] and "recv" or "send",
            opcode = pkt[3],
            size = pkt[4]
        })
    end
    return result
end
