--- Extends Objects With Additional Functionality
--
local Extensions = {}
local AddOnName, nsVars = ...
local ss = nsVars.SS

local type, pairs = type, pairs
local IsShiftKeyDown = IsShiftKeyDown

--- Calls SetVertexColor using table values.
--- @param obj table The Frame object.
--- @param color table The color table.
local function SetVertColor(obj, color)
  obj:SetVertexColor(ss.Utils:GetRGBAValues(color))
end

--- Animates an alpha change.
--- @param display table The Frame.
--- @param target number The target alpha.
--- @param onComplete function Completion handler. (Optional)
--- @param onUpdate function OnUpdate handler. (Optional)
local function Fade(display, target, onComplete, onUpdate)
  local fadeDuration = 0.1
  target = target or 0

  if target == display.fadeTarget then
    return
  end

  ss.Extensions:Hook(display.fadeAnimGroup, "OnFinished", nil)
  ss.Extensions:Hook(display.fadeAnimGroup, "OnUpdate", nil)

  display.fadeTarget = target
  display.fadeOnComplete = onComplete
  display.fadeOnUpdate = onUpdate

  -- Retrieve current values to handle ongoing animations.
  local dur = (display.fadeAnimGroup.fade:GetProgress() * fadeDuration) or fadeDuration

  if not display.fadeAnimGroup:IsDone() then
    display.fadeAnimGroup:Stop()
  else
    dur = fadeDuration
  end

  -- Set animation values.
  display.fadeAnimGroup.fade:SetFromAlpha(display.fadeCurAlpha or display:GetAlpha())
  display.fadeAnimGroup.fade:SetToAlpha(target)
  display.fadeAnimGroup.fade:SetDuration(dur)
  display.fadeAnimGroup:Play()

  ss.Extensions:Hook(display.fadeAnimGroup, "OnFinished", function()
    -- Apply the alpha change.
    display:SetAlpha(display.fadeTarget)
    display.fadeCurAlpha = display.fadeTarget

    if type(display.fadeOnComplete) == "function" then
      display.fadeOnComplete(display)
    end
    display.fadeOnComplete = nil
    display.fadeOnUpdate = nil
  end)

  ss.Extensions:Hook(display.fadeAnimGroup, "OnUpdate", function()
    -- Ensure the unit frames is responsive imediately.
    display.fadeCurAlpha = display:GetAlpha()

    if type(display.fadeOnUpdate) == "function" then
      display.fadeOnUpdate(display)
    end
  end)
end

--- Adds the SetVertColor functionality.
--- @param ... One or more Frames.
function Extensions:DelegateVertexColor(...)
  local obj = { ... }

  for _, v in pairs(obj) do
    if not v.SetVertexColor then
      ss.Utils.throw(ss.Utils:stringf("Object (%s) does not implement SetVertexColor", type(v)), 2)
    else
      v.SetVertColor = SetVertColor
    end
  end
end

function Extensions:Hook(frame, event, hookFunc, tag)
  frame["_hook" .. event] = frame["_hook" .. event] or {}
  frame["_hook" .. event][tag or "_"] = hookFunc

  if not frame["_hooked" .. event] then
    frame["_hooked" .. event] = true

    frame:HookScript(event, function(self, ...)
      for _, hook in pairs(self["_hook" .. event]) do
        hook(self, ...)
      end
    end)
  end
end

--- Makes a frame movable whike holding the selected key.
--- @param frame table The target frame.
--- @param button table The button to hold, defaults to "LeftButton".
--- @param listener function optional callback for "OnDragStop".
function Extensions:MakeMovable(frame, button, listener)
  if type(frame) == "table" then
    frame:SetClampedToScreen(true)
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag(button or "LeftButton")

    self:Hook(frame, "OnDragStart", function(self)
      if IsShiftKeyDown() then
        self:StartMoving()
      end
    end)

    self:Hook(frame, "OnDragStop", function(self)
      self:StopMovingOrSizing()

      if listener then
        listener(self)
      end
    end)
  else
    ss.Utils.throw(ss.Utils.Errors.ErrorInvalidArgs, "Extensions", "MakeMovable",
        { "frame", "Frame", frame }
    )
  end
end

--- Makes a frame movable whike holding the selected key.
--- @param ... One or more Frames.
function Extensions:MakeFadable(...)
  local frames = { ... }

  for _, frame in pairs(frames) do
    if type(frame) == "table" then
      frame.fadeCurAlpha = frame:GetAlpha()
      frame.fadeTarget = frame.fadeCurAlpha
      frame.fadeAnimGroup = frame.fadeAnimGroup or frame:CreateAnimationGroup()
      frame.fadeAnimGroup.fade = frame.fadeAnimGroup.fade or frame.fadeAnimGroup:CreateAnimation("Alpha")
      frame.Fade = Fade
    else
      ss.Utils.throw(ss.Utils.Errors.ErrorInvalidArgs, "Extensions", "MakeFadable",
          { "frame", "Frame", frame }
      )
    end
  end
end

local mtAddable = {
  __add = function(a, b)
    local r = {}
    for _, v in pairs(a) do
      table.insert(r, v)
    end
    for _, v in pairs(b) do
      table.insert(r, v)
    end
    return r
  end,
}

local mtJoinable = {
  __add = function(a, b)
    local r = {}
    for k, v in pairs(a) do
      r[k] = v
    end
    for k, v in pairs(b) do
      r[k] = v
    end
    return r
  end,
}

--- Allows the values of two tables to be merged.
--- @param t table The target frame.
function Extensions:MakeAddable(t)
  setmetatable(t, mtAddable)
end

--- Allows two tables to be merged by unique index.
--- @param t table The target frame.
function Extensions:MakeJoinable(t)
  setmetatable(t, mtJoinable)
end

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