---@class addonTablePlatynator
local addonTable = select(2, ...)

addonTable.Display.CastBarMixin = {}

local ConvertColor = addonTable.Display.Utilities.ConvertColor

local GetInterruptSpell = addonTable.Display.Utilities.GetInterruptSpellPriority

function addonTable.Display.CastBarMixin:PostInit()
  if self.details.background.applyColor then -- Apply tint to colours
    self.modColors = addonTable.Display.Utilities.TintAutoColors(self.details.autoColors, self.details.background.color)
  end

  self.showInterruptMarker = self.details.interruptMarker.asset ~= "none"
end

function addonTable.Display.CastBarMixin:SetUnit(unit)
  self.unit = unit
  if self.unit then
    self.interrupted = nil
    self:RegisterUnitEvent("UNIT_SPELLCAST_START", self.unit)
    self:RegisterUnitEvent("UNIT_SPELLCAST_STOP", self.unit)
    self:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", self.unit)

    self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", self.unit)
    self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", self.unit)
    self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", self.unit)

    if addonTable.Constants.IsRetail then
      self:RegisterUnitEvent("UNIT_SPELLCAST_EMPOWER_START", self.unit)
      self:RegisterUnitEvent("UNIT_SPELLCAST_EMPOWER_STOP", self.unit)
      self:RegisterUnitEvent("UNIT_SPELLCAST_EMPOWER_UPDATE", self.unit)
    end

    self:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", self.unit)
    self:RegisterUnitEvent("UNIT_SPELLCAST_FAILED", self.unit)

    self:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTIBLE", self.unit)
    self:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", self.unit)

    if self.showInterruptMarker then
      self:RegisterEvent("SPELL_UPDATE_USABLE")
    end

    self:ApplyCasting()

    addonTable.Display.RegisterForColorEvents(self, self.details.autoColors)
    self:SetColor(addonTable.Display.GetColor(self.details.autoColors, self.colorState, self.unit))
  else
    self:StripInternal()
  end
end

function addonTable.Display.CastBarMixin:StripInternal()
  self:RefreshInterruptMarker()
  if self.timer then
    self.timer:Cancel()
    self.timer = nil
  end
  self.interrupted = nil

  self:UnregisterAllEvents()
  addonTable.Display.UnregisterForColorEvents(self)
  self:SetScript("OnUpdate", nil)
end

function addonTable.Display.CastBarMixin:Strip()
  self:StripInternal()
  self.modColors = nil
end

function addonTable.Display.CastBarMixin:OnEvent(eventName, ...)
  if eventName == "UNIT_SPELLCAST_INTERRUPTED" or eventName == "UNIT_SPELLCAST_CHANNEL_STOP" and select(4, ...) ~= nil or eventName == "UNIT_SPELLCAST_EMPOWER_STOP" and select(5, ...) ~= nil then
    self.interrupted = true
    self:Show()
    self.statusBar:SetMinMaxValues(0, 1)
    self.statusBar:SetValue(1)
    if self.timer then
      self.timer:Cancel()
      self.timer = nil
    end
    self.timer = C_Timer.NewTimer(0.8, function()
      if self.interrupted then
        self.interrupted = nil
        self:Hide()
      end
    end)
    self:SetScript("OnUpdate", nil)
    self.interruptMarker:Hide()
  elseif eventName == "UNIT_SPELLCAST_DELAYED" or eventName == "UNIT_SPELLCAST_CHANNEL_UPDATE" or eventName == "UNIT_SPELLCAST_EMPOWER_UPDATE" then
    if self:IsShown() then
      self:ApplyCasting()
    end
  elseif eventName == "UNIT_SPELLCAST_CHANNEL_STOP" or eventName == "UNIT_SPELLCAST_EMPOWER_STOP" or eventName == "UNIT_SPELLCAST_STOP" then
    self:ClearCast()
  elseif eventName == "SPELL_UPDATE_USABLE" and self.showInterruptMarker then
    self:RefreshInterruptMarker()
  elseif eventName:match("^UNIT_SPELL") then
    self:ApplyCasting()
  end

  if self:IsShown() then
    self:ColorEventHandler(eventName)
  end
end

function addonTable.Display.CastBarMixin:SetColor(...)
  self.statusBar:GetStatusBarTexture():SetVertexColor(...)
  self.marker:SetVertexColor(...)
  if self.details.background.applyColor then
    local mod = self.details.background.color
    if self.modColors then
      self.background:SetVertexColor(addonTable.Display.GetColor(self.modColors, self.colorState, self.unit))
    else
      local r, g, b = ...
      self.background:SetVertexColor(r, g, b, mod.a)
    end
  end
end

function addonTable.Display.CastBarMixin:ClearCast()
  if not self.interrupted then
    if self.timer then
      self.timer:Cancel()
      self.timer = nil
    end
    self:Hide()
  end
  self.isChanneled = nil
  self.notInterruptible = nil
  self.uninterruptibleCheck = nil
end

if UnitCastingDuration then
  function addonTable.Display.CastBarMixin:ApplyCasting()
    self.isChanneled = false
    local isEmpowered = false
    local castDuration = UnitCastingDuration(self.unit)
    if not castDuration then
      castDuration = UnitEmpoweredChannelDuration(self.unit, true)
      self.isChanneled = true
      if castDuration then
        isEmpowered = true
      end
    end
    if not castDuration then
      castDuration = UnitChannelDuration(self.unit)
      self.isChanneled = true
    end
    if castDuration ~= nil then
      local notInterruptible, _
      if self.isChanneled then
        _, _, _, _, _, _, notInterruptible, _ = UnitChannelInfo(self.unit)
      else
        _, _, _, _, _, _, _, notInterruptible, _ = UnitCastingInfo(self.unit)
      end

      self.interrupted = nil

      if self.timer then
        self.timer:Cancel()
        self.timer = nil
      end

      self:Show()

      self.statusBar:SetTimerDuration(castDuration, nil, self.isChanneled and not isEmpowered and Enum.StatusBarTimerDirection.RemainingTime or Enum.StatusBarTimerDirection.ElapsedTime)
      local spellID
      if self.showInterruptMarker then
        spellID = GetInterruptSpell()
      end
      self.interruptMarker:SetShown(spellID ~= nil)
      self.interruptPositioner:SetShown(spellID ~= nil)
      if spellID then
        self:ReverseInterruptMarker(self.isChanneled and not isEmpowered)
        local interruptDuration = C_Spell.GetSpellCooldownDuration(spellID)
        self.interruptPositioner:SetMinMaxValues(0, castDuration:GetTotalDuration())
        self.interruptMarker:SetMinMaxValues(0, castDuration:GetTotalDuration())
        self.uninterruptibleCheck = C_CurveUtil.EvaluateColorValueFromBoolean(notInterruptible, 0, 1)
        self.interruptPositioner:SetValue(castDuration:GetElapsedDuration())
        self.interruptMarker:SetValue(interruptDuration:GetRemainingDuration())
        self:RefreshInterruptMarker()
        self.timer = C_Timer.NewTicker(0.1, function()
          self:RefreshInterruptMarker()
        end)
      else
        self.isChanneled = nil
      end
    else
      self:ClearCast()
    end
  end

  function addonTable.Display.CastBarMixin:RefreshInterruptMarker()
    if self.isChanneled == nil then
      return
    end
    local spellID = GetInterruptSpell()
    if spellID then
      local interruptDuration = C_Spell.GetSpellCooldownDuration(spellID)
      self.uninterruptibleCheck = C_CurveUtil.EvaluateColorValueFromBoolean(interruptDuration:IsZero(), 0, self.uninterruptibleCheck)
      self.interruptMarker:SetAlpha(self.uninterruptibleCheck)
    end
  end
else
  function addonTable.Display.CastBarMixin:ApplyCasting()
    local name, text, texture, startTime, endTime, _, _, notInterruptible, spellID = UnitCastingInfo(self.unit)
    self.isChanneled = false

    if name == nil then
      name, text, texture, startTime, endTime, _, notInterruptible, spellID = UnitChannelInfo(self.unit)
      self.isChanneled = true
    end

    if name ~= nil then
      self.interrupted = nil
      self.notInterruptible = notInterruptible

      self:Show()

      if self.timer then
        self.timer:Cancel()
        self.timer = nil
      end

      local castEnd = (endTime - startTime) / 1000
      self.statusBar:SetMinMaxValues(0, castEnd)
      local castValue = GetTime() - startTime / 1000
      self.statusBar:SetValue(castValue)

      local spellID
      if self.showInterruptMarker and not notInterruptible then
        spellID = GetInterruptSpell()
      end
      self.interruptMarker:SetShown(spellID ~= nil)
      self.interruptPositioner:SetShown(spellID ~= nil)
      if spellID then
        self.interruptPositioner:SetMinMaxValues(0, castEnd)
        self.interruptMarker:SetMinMaxValues(0, castEnd)
        local info = C_Spell.GetSpellCooldown(spellID)
        local interruptEndTime = info.duration + info.startTime
        if interruptEndTime > 0 then
          self:RefreshInterruptMarker()
          self.interruptMarker:Show()
          self.interruptPositioner:SetValue(castValue)
          self.interruptMarker:SetValue(interruptEndTime - GetTime())
          self.timer = C_Timer.NewTicker(0.1, function()
            self:RefreshInterruptMarker()
          end)
        else
          self.interruptMarker:Hide()
        end
      end

      if self.isChanneled then
        self.timer = C_Timer.NewTicker(0.005, function()
          self.statusBar:SetValue(endTime / 1000 - GetTime())
        end)
        self.statusBar:SetValue(endTime / 1000 - GetTime())
      else
        self.timer = C_Timer.NewTicker(0.005, function()
          self.statusBar:SetValue(GetTime() - startTime / 1000)
        end)
        self.statusBar:SetValue(GetTime() - startTime / 1000)
      end
    else
      self:ClearCast()
    end
  end

  function addonTable.Display.CastBarMixin:RefreshInterruptMarker()
    if self.isChanneled == nil then
      return
    end
    local spellID = GetInterruptSpell()
    if spellID and not self.notInterruptible and self.interruptMarker:IsShown() then
      local info = C_Spell.GetSpellCooldown(spellID)
      local endTime = info.duration + info.startTime
      self.interruptMarker:SetShown(endTime > 0)
    end
  end
end
