OverInteractive = OverInteractive or {}

local COMMANDS_FILE  = "OverInteractiveCommands.txt"
local RESPONSES_FILE = "OverInteractiveResponses.txt"

-- Throttle: sondear archivos cada 6 frames (~100ms a 60fps), igual que CrowdControl
local FRAME_INTERVAL = 6
local _frameCount    = 0

-- Ojo: NO se chequea isGamePaused(). PZ pausa al perder el foco de la ventana
-- (options.ini → focusloss=true), así que con ese gate cualquier comando
-- disparado mientras el streamer está en otra ventana se rechazaba. Los efectos
-- son mutaciones de estado: se aplican igual y se ven al despausar.
function OverInteractive.IsReady()
    if not isIngameState()  then return false end
    local player = getPlayer()
    if not player then return false end
    if player:getBodyDamage():getOverallBodyHealth() == 0 then return false end
    return true
end

-- Empaqueta el comando para el servidor. sendClientCommand solo transporta una
-- tabla plana, así que los parámetros van como p1..pN con su cuenta en n.
function OverInteractive.Relay(cmdObj)
    local t = {
        code   = cmdObj.code,
        viewer = cmdObj.viewer or "El Chat",
        target = cmdObj.target or "all",
        n      = 0,
    }
    local params = cmdObj.parameters or {}
    for i = 1, #params do
        t["p" .. i] = params[i]
        t.n = i
    end
    sendClientCommand("OverInteractive", "invoke", t)
end

function OverInteractive.ProcessCommand(cmdObj)
    if not cmdObj.type or not cmdObj.id then return end

    local response = {
        id       = cmdObj.id,
        type     = cmdObj.type == "ping" and "pong" or "invoke_result",
        status   = "ok",
        cooldown = 0,
    }

    if cmdObj.type == "invoke" then
        if not OverInteractive.IsReady() then
            response.status = "error"
            response.reason = "Game not ready"
        elseif not (OverInteractive.Actions and OverInteractive.Actions[cmdObj.code]) then
            response.status = "error"
            response.reason = "Unknown command: " .. tostring(cmdObj.code)
        elseif isClient() then
            -- Partida hosteada: el mundo y los demás jugadores los maneja el
            -- servidor. Se releva y se responde ok sin esperar: el canal de
            -- archivos es del cliente y no hay vuelta desde el server.
            local ok, msg = pcall(function()
                OverInteractive.Relay(cmdObj)
            end)
            if not ok then
                response.status = "error"
                response.reason = tostring(msg)
            end
        else
            -- Single player: se ejecuta acá mismo sobre el jugador local.
            local ok, msg = pcall(function()
                OverInteractive.Actions[cmdObj.code](
                    getPlayer(), cmdObj.parameters or {}, cmdObj.viewer or "El Chat")
            end)
            if not ok then
                response.status = "error"
                response.reason = tostring(msg)
            end
        end
    end

    local writer = getFileWriter(RESPONSES_FILE, true, true)
    if writer then
        local json = '{"id":' .. tostring(response.id)
                  .. ',"type":"' .. response.type
                  .. '","status":"' .. response.status .. '"'
        if response.reason then
            json = json .. ',"reason":"' .. string.gsub(response.reason, '"', '\\"') .. '"'
        end
        json = json .. ',"cooldown":' .. tostring(response.cooldown) .. '}\n'
        writer:write(json)
        writer:close()
    end
end

local function OnPostRender()
    _frameCount = _frameCount + 1
    if _frameCount < FRAME_INTERVAL then return end
    _frameCount = 0

    local reader = getFileReader(COMMANDS_FILE, true)
    if not reader then return end

    local hasLines = false
    local line = reader:readLine()

    if line and line ~= "" then
        hasLines = true
        while line do
            if line ~= "" then
                local idStr   = string.match(line, '"id":%s*(%d+)')
                local typeStr = string.match(line, '"type":%s*"([^"]+)"')
                local codeStr = string.match(line, '"code":%s*"([^"]+)"')

                if idStr and typeStr then
                    -- Extract all parameters; last element is always the viewer name
                    local paramStr = string.match(line, '"parameters":%[(.-)%]')
                    local params = {}
                    if paramStr then
                        for v in string.gmatch(paramStr, '"([^"]*)"') do
                            table.insert(params, v)
                        end
                    end
                    local viewer = (#params > 0 and params[#params] ~= "") and params[#params] or "El Chat"
                    local actionArgs = {}
                    for i = 1, #params - 1 do actionArgs[i] = params[i] end
                    OverInteractive.ProcessCommand({
                        id         = tonumber(idStr),
                        type       = typeStr,
                        code       = codeStr,
                        parameters = actionArgs,
                        viewer     = viewer,
                        -- Modo de objetivo elegido en la app; solo aplica hosteado.
                        target     = string.match(line, '"target":%s*"([^"]+)"'),
                    })
                end
            end
            line = reader:readLine()
        end
    end

    reader:close()

    if hasLines then
        local writer = getFileWriter(COMMANDS_FILE, false, false)
        if writer then
            writer:write("")
            writer:close()
        end
    end
end

Events.OnPostRender.Add(OnPostRender)

-- Hosteado, los comandos de personaje los manda el servidor a cada afectado para
-- que los aplique sobre SU jugador: el cliente es autoritativo sobre su personaje
-- y lo que escriba el servidor se pierde en el siguiente update.
local function OnServerCommand(module, command, args)
    if module ~= "OverInteractive" or command ~= "apply" then return end
    if not args or not args.code then return end

    local player = getPlayer()
    local action = OverInteractive.Actions and OverInteractive.Actions[args.code]
    if not player or not action then return end

    local params = {}
    for i = 1, tonumber(args.n) or 0 do
        params[i] = args["p" .. i]
    end

    local ok, msg = pcall(function()
        action(player, params, args.viewer or "El Chat")
    end)
    if not ok then
        print("[OverInteractive] " .. tostring(args.code) .. " fallo: " .. tostring(msg))
    end
end

Events.OnServerCommand.Add(OnServerCommand)
