--- Pending Task
--
local PendingTask = {}
local AddOnName, nsVars = ...
local ss = nsVars.SS

local InCombatLockdown, GetTime = InCombatLockdown, GetTime
local table, pairs, type, next, unpack = table, pairs, type, next, unpack
local ErrorInvalidArgs = ss.Utils.Errors.ErrorInvalidArgs
local throw = ss.Utils.throw
local CreateFrame = ss.CreateFrame

local pendingTasks = {}
local delayedTasks = {}

local eventFrame = CreateFrame("Frame")

eventFrame:HookScript("OnUpdate", function()
  -- Attempt to complete delayed tasks.
  for k, v in pairs(delayedTasks) do
    -- Use the function call as part of the validation.
    if type(v) ~= "function" or v() == true then
      -- if the result of v() is true, the task was completed.
      table.remove(delayedTasks, k)
    end
  end
end)

--- Completes all pending tasks.
-- @param notify Flag to display a message to the use. (Optional)
--
function PendingTask.completeAll(notify)
  -- Check for pending tasks
  if next(pendingTasks) == nil then
    return
  end

  local completed = {};

  for k, v in pairs(pendingTasks) do
    if InCombatLockdown() then
      break
    end

    if type(v._f) == "function" then
      v._f()
      if notify and v._t then
        ss.Utils:printf(ss.T["msg_task_completed"], AddOnName, v._t)
      end
    elseif notify and v._t then
      ss.Utils:printf(ss.T["msg_task_failed"], AddOnName, v._t)
    end

    table.insert(completed, k)
  end

  for _, v in pairs(completed) do
    pendingTasks[v] = nil
  end
end

--- Adds a task to execute at a later time.
-- @param name The name of the task.
-- @param task The function to call.
-- @param text The text to display to the user. (Optional)
--
function PendingTask.add(name, task, text)
  if not InCombatLockdown() and ss.playerLoggedIn then
    task()
    return
  end

  -- TODO: If running, run dont add.
  pendingTasks[name] = {
    _f = task,
    _t = text,
  }

  if ss.playerLoggedIn and text then
    ss.Utils:printf(ss.T["msg_will_be_called"], AddOnName, text)
  end
end

--- Removes a pending task.
-- @param name The name of the task.
--
function PendingTask.remove(name)
  pendingTasks[name] = nil
end

--- Calls a function after a given duration.
--
-- Usage:
--
-- ss.PendingTask.delay(5, function(var)
--   print(var)
-- end, params)
--
-- @param delay The execution delay.
-- @param task The function to call.
-- @param ... The function parameters. (Optional)
--
function PendingTask.delay(delay, task, ...)
  if type(delay) == "number" and type(task) == "function" then
    local time = GetTime()
    local params = { ... }

    -- Wrap the task in a function tracking elapsed time.
    local function delayedTask()
      if GetTime() >= time + delay then
        task(unpack(params))

        -- Return true to notify completion.
        return true
      end
    end

    table.insert(delayedTasks, delayedTask)
  else
    throw(ErrorInvalidArgs, "PendingTask", "delay",
        { "delay", "number", delay },
        { "task", "function", task }
    )
  end

end

ss.Utils.addModule("PendingTask", PendingTask)
