
-- TriggerSystem.lua
-- Core implementation of the event/trigger system.
-- Handles registration, evaluation, and execution of all game-defined triggers.

---@class Trigger
---@field conditions function[]|string[]|nil
---@field action function|string
---@field event event_type
---@field triggerData table


require "debug"

-- Check if the function relies on a specific environment (e.g., game state)
local function validateClosure(func)
    if type(func) == "function" then
        -- Check if function has upvalues
        local upvalues = {}
        local i = 1
        while true do
            local name, value = debug.getupvalue(func, i)
            if not name then break end
            table.insert(upvalues, {name = name, value = value})
            i = i + 1
        end

        -- If any upvalue is not nil, then the function might be problematic
        for _, upvalue in ipairs(upvalues) do
            if type(upvalue.value) == "table" or type(upvalue.value) == "function" then
                -- The function might be relying on a mutable object, potentially problematic during serialization
                error("Function has upvalue '" .. upvalue.name .. "' which may cause issues during serialization.")
            end
        end
    end
end
local function getFunctionName(func)
    local info = debug.getinfo(func, "n")
    if info and info.name and info.name ~= "" then
        return info.name
    end

    for k, v in pairs(_G) do
        if v == func then
            return k
        end
    end

    return "<anonymous>"
end

--- @param func function|string
local function validatefunc(func)
    --complex function are hard to serialize, so simply store the name if they're global,
    --anonymous ones tend to be simpler, so we can store them as is
    if type(func) == "function" then
        local fname = getFunctionName(func)
        if fname == "<anonymous>" then
            validateClosure(func)
            return func
        elseif _G[fname] == nil then
             error("Function '" .. fname .. "' not found, make sure it is either defined globally or anonymously")
        else
            return fname
        end
    elseif type(func) == "string" then
        if _G[func] == nil then
            error("Function '" .. func .. "' not found, make sure it is defined globally")
        end
        return func
    else
        error("Param not a function but " .. type(func))
    end
end

--- Creates a new trigger and returns it
---@param event event_type
---@param action function|string
---@param triggerData? table
---@return table
function CreateTrigger(event,action,triggerData)
    action = validatefunc(action)
    Game.triggers = Game.triggers or {}
    local trigger = { event = event, conditions = {}, action = action, triggerData = triggerData }
    table.insert(Game.triggers, trigger)
    return trigger
end

--- Adds a condition function that needs to evaluate to true for the actions to be triggered when the event happens
--- @param trigger Trigger
--- @param condition function|string Function that returns true or false
function TriggerAddCondition(trigger, condition)
    condition = validatefunc(condition)
    trigger.conditions = trigger.conditions or {}
    table.insert(trigger.conditions, condition)
end

---@param func function|string
local function pfunc(func,eventData,triggerData)
    if type(func) == "function" then
        return pcall(func,eventData,triggerData)
    elseif type(func) == "string" then
        return pcall(_G[func],eventData,triggerData)
    end
end

--- Processes a trigger
--- @param trigger Trigger The trigger to process
--- @param eventData table The data generated by the event
--- @param errors table A table to store errors in
local function ProcessTrigger(trigger,eventData, errors)
    local allConditionsMet = true
    if trigger.conditions then
        for _, condition in ipairs(trigger.conditions) do
            local success, val = pfunc(condition,eventData,trigger.triggerData)
            if ( success == false) then
                --condition errored out, assume it'll never be true again, and destroy the trigger
                trigger.triggerData = trigger.triggerData or {}
                trigger.triggerData.destroyAfterUse = true
                return true
            
            elseif val == false then
                allConditionsMet = false
                break
            end
        end
    end
    if allConditionsMet then
        local success, error = pfunc(trigger.action,eventData,trigger.triggerData)
        if not success then
            table.insert(errors,error)
        end
        return true
    end
    return false
end

--- goes through all triggers and checks if they have an event that matches the eventType
--- @param eventType event_type
--- @param eventData table
function ProcessEvent(eventType, eventData)
    Game.triggers = Game.triggers or {}
    local errors = {}

    --go backwards to allow removing triggers
    for i = #Game.triggers, 1, -1 do
        local trigger = Game.triggers[i]
        if trigger.event == eventType then
            if ProcessTrigger(trigger, eventData, errors) then
                if trigger.triggerData.destroyAfterUse == true then
                    table.remove(Game.triggers, i) -- Remove the trigger safely
                end
            end
        end
    end
    if #errors > 0 then
        for _, error in ipairs(errors) do
            print(eventType .. ": Error in trigger: " .. error)
        end
    end
end
