local E, L, V, P, G = unpack(ElvUI)
local UF = E:GetModule('UnitFrames')
local LSM = E.Libs.LSM
local ElvUF = E.oUF

local abs, next = abs, next
local utf8sub = string.utf8sub

local CreateFrame = CreateFrame
local GetTime = GetTime
local UnitCanAttack = UnitCanAttack
local UnitClass = UnitClass
local UnitIsPlayer = UnitIsPlayer
local UnitName = UnitName
local UnitReaction = UnitReaction
local UnitSpellHaste = UnitSpellHaste

local IsSpellInSpellBook = C_SpellBook.IsSpellInSpellBook or IsSpellKnownOrOverridesKnown
local IsSpellKnown = C_SpellBook.IsSpellKnown or IsPlayerSpell
local StatusBarInterpolation = Enum.StatusBarInterpolation

do
	function UF:CastBar_UpdatePip(castbar, pip, stage)
		local color = castbar.pipColor and castbar.pipColor[stage]
		if color then
			pip.texture:SetVertexColor(color.r, color.g, color.b, color.a)
		end
	end

	local pipMapAlpha = {2, 3, 4, 1, 5}
	function UF:UpdatePipStep(stage) -- self is element
		if not self.Pips then return end

		local onlyThree = (stage == 3 and self.numStages == 3) and 4
		local pip = self.Pips[pipMapAlpha[onlyThree or stage]]
		if not pip then return end

		-- pip.texture:SetAlpha(1)

		E:UIFrameFadeOut(pip.texture, pip.pipTimer, pip.pipStart, pip.pipFaded)
	end
end

function UF:PostUpdatePips(stages)
	if not self.Pips or not self.setupPips then
		return -- we only need this once
	end

	self.setupPips = nil -- clear this

	for stage in next, stages do
		local pip = self.Pips[stage]
		if not pip then break end

		UF.PostUpdatePip(self, pip, stage)
	end
end

function UF:PostUpdatePip(pip, stage, stages) -- self is element
	-- pip.texture:SetAlpha(pip.pipAlpha or 1)
	-- currently UpdatePipStep is broken: pip.pipAlpha or 1

	local reverse = self:GetReverseFill()
	local nextPip = self.Pips and self.Pips[stage + 1]
	local anchor = nextPip or self
	if reverse then
		pip.texture:Point('LEFT', anchor, 4, 0)
		pip.texture:Point('RIGHT', -4, 0)
	else
		pip.texture:Point('LEFT', 4, 0)
		pip.texture:Point('RIGHT', anchor, -4, 0)
	end
end

function UF:CreatePip(stage)
	local pip = CreateFrame('Frame', nil, self, 'CastingBarFrameStagePipTemplate')

	-- clear the original art (the line)
	if pip.BasePip then
		pip.BasePip:SetAlpha(0)
	end

	-- create the texture
	pip.texture = pip:CreateTexture(nil, 'ARTWORK', nil, 2)
	pip.texture:Point('BOTTOM')
	pip.texture:Point('TOP')

	-- values for the animation
	pip.pipStart = 1.0 -- alpha on hit
	pip.pipAlpha = 0.3 -- alpha on init
	pip.pipFaded = 0.6 -- alpha when passed
	pip.pipTimer = 0.4 -- fading time to passed

	-- self is the castbar
	if self.ModuleStatusBars then
		self.ModuleStatusBars[pip.texture] = true
	end

	-- update colors
	UF:CastBar_UpdatePip(self, pip, stage)

	return pip
end

function UF:BuildPip(stage)
	local pip = UF.CreatePip(self, stage)

	UF:Update_StatusBar(pip.texture)

	return pip
end

function UF:Construct_Castbar(frame, moverName)
	local castbar = CreateFrame('StatusBar', '$parent_CastBar', frame)
	castbar:SetFrameLevel(frame.RaisedElementParent.CastBarLevel)

	UF.statusbars[castbar] = 'castbar'
	castbar.ModuleStatusBars = UF.statusbars -- not oUF

	castbar.CustomDelayText = UF.CustomCastDelayText
	castbar.CustomTimeText = UF.CustomTimeText
	castbar.PostCastStart = UF.PostCastStart
	castbar.PostCastStop = UF.PostCastStop
	castbar.PostCastInterruptible = UF.PostCastInterruptible
	castbar.PostCastFail = UF.PostCastFail
	castbar.UpdatePipStep = UF.UpdatePipStep
	castbar.PostUpdatePip = UF.PostUpdatePip
	castbar.PostUpdatePips = UF.PostUpdatePips
	castbar.CreatePip = UF.BuildPip
	castbar.TickLines = {}

	castbar.setupPips = true
	castbar:SetClampedToScreen(true)
	castbar:CreateBackdrop(nil, nil, nil, nil, true)

	castbar.Time = castbar:CreateFontString(nil, 'OVERLAY')
	castbar.Time:Point('RIGHT', castbar, 'RIGHT', -4, 0)
	castbar.Time:SetTextColor(0.84, 0.75, 0.65)
	castbar.Time:SetJustifyH('RIGHT')
	castbar.Time:SetWordWrap(false)
	castbar.Time:FontTemplate()

	castbar.Text = castbar:CreateFontString(nil, 'OVERLAY')
	castbar.Text:Point('LEFT', castbar, 'LEFT', 4, 0)
	castbar.Text:Point('RIGHT', castbar.Time, 'LEFT', -4, 0)
	castbar.Text:SetTextColor(0.84, 0.75, 0.65)
	castbar.Text:SetJustifyH('LEFT')
	castbar.Text:SetWordWrap(false)
	castbar.Text:FontTemplate()

	castbar.Shield = castbar:CreateTexture(nil, 'OVERLAY', nil, 2)
	castbar.Shield:SetTexture(E.media.blankTex)
	castbar.Shield:SetAlpha(0) -- disable is so its hidden on classic

	--Set to castbar.SafeZone
	castbar.LatencyTexture = castbar:CreateTexture(nil, 'OVERLAY', nil, 3)
	castbar.LatencyTexture:SetTexture(E.media.blankTex)
	castbar.LatencyTexture:SetVertexColor(0.69, 0.31, 0.31, 0.75)

	-- ticks are overlay 4 (under spark)
	castbar.Spark_ = castbar:CreateTexture(nil, 'OVERLAY', nil, 5)
	castbar.Spark_:SetTexture(E.media.blankTex)
	castbar.Spark_:SetVertexColor(0.9, 0.9, 0.9, 0.6)
	castbar.Spark_:SetBlendMode('ADD')
	castbar.Spark_:Width(2)

	castbar.bg = castbar:CreateTexture(nil, 'BORDER')
	castbar.bg:SetTexture(E.media.blankTex)
	castbar.bg:SetAllPoints()

	local button = CreateFrame('Frame', nil, castbar)
	local holder = CreateFrame('Frame', nil, castbar)
	button:SetTemplate(nil, nil, nil, nil, true)

	castbar.Holder = holder
	--these are placeholder so the mover can be created.. it will be changed.
	castbar.Holder:Point('TOPLEFT', frame, 'BOTTOMLEFT', 0, -(UF.BORDER - UF.SPACING))
	castbar:Point('BOTTOMLEFT', castbar.Holder, 'BOTTOMLEFT', UF.BORDER, UF.BORDER)
	button:Point('RIGHT', castbar, 'LEFT', -UF.SPACING*3, 0)

	if moverName then
		local name = frame:GetName()
		local configName = name:gsub('^ElvUF_', ''):lower()
		E:CreateMover(castbar.Holder, name..'CastbarMover', moverName, nil, -6, nil, 'ALL,SOLO', nil, 'unitframe,individualUnits,'..configName..',castbar')
	end

	local icon = button:CreateTexture(nil, 'ARTWORK')
	icon:SetInside(nil, UF.BORDER, UF.BORDER)
	icon.bg = button

	--Set to castbar.Icon
	castbar.ButtonIcon = icon

	return castbar
end

function UF:Configure_Castbar(frame)
	local castbar = frame.Castbar
	local db = frame.db.castbar

	local SPACING1 = UF.BORDER + UF.SPACING
	local SPACING2 = SPACING1 * 2

	if E.Retail then
		castbar.smoothing = (db.smoothbars and StatusBarInterpolation.ExponentialEaseOut) or StatusBarInterpolation.Immediate or nil
	else
		E:SetSmoothing(castbar, db.smoothbars)
	end

	castbar.timeToHold = db.timeToHold
	castbar:SetReverseFill(db.reverse)
	castbar:ClearAllPoints()

	castbar.Holder:Size(db.width, db.height)
	local oSC = castbar.Holder:GetScript('OnSizeChanged')
	if oSC then oSC(castbar.Holder) end

	castbar:SetFrameStrata(db.strataAndLevel and db.strataAndLevel.useCustomStrata and db.strataAndLevel.frameStrata or 'LOW')
	castbar:SetFrameLevel((db.strataAndLevel and db.strataAndLevel.useCustomLevel and db.strataAndLevel.frameLevel) or (frame.RaisedElementParent and frame.RaisedElementParent.CastBarLevel) or 1)

	--Empowered
	castbar.pipColor = UF.db.colors.empoweredCast

	if castbar.Pips then
		for stage, pip in next, castbar.Pips do
			UF:CastBar_UpdatePip(castbar, pip, stage)
		end
	end

	--Latency
	if frame.unit == 'player' and db.latency then
		castbar.SafeZone = castbar.LatencyTexture
		castbar.LatencyTexture:Show()
	else
		castbar.SafeZone = nil
		castbar.LatencyTexture:Hide()
	end

	--Font Options
	local customFont = db.customTextFont
	if customFont.enable then
		castbar.Text:FontTemplate(LSM:Fetch('font', customFont.font), customFont.fontSize, customFont.fontStyle)
	else
		UF:Update_FontString(castbar.Text)
	end

	customFont = db.customTimeFont
	if customFont.enable then
		castbar.Time:FontTemplate(LSM:Fetch('font', customFont.font), customFont.fontSize, customFont.fontStyle)
	else
		UF:Update_FontString(castbar.Time)
	end

	local textColor = E:UpdateClassColor(db.textColor)
	castbar.Text:SetTextColor(textColor.r, textColor.g, textColor.b)
	castbar.Time:SetTextColor(textColor.r, textColor.g, textColor.b)

	castbar.Text:Point('LEFT', castbar, 'LEFT', db.xOffsetText, db.yOffsetText)
	castbar.Time:Point('RIGHT', castbar, 'RIGHT', db.xOffsetTime, db.yOffsetTime)

	local barTexture = castbar:GetStatusBarTexture()
	castbar.Shield:ClearAllPoints()
	castbar.Shield:Point(db.reverse and 'LEFT' or 'RIGHT', barTexture)
	castbar.Shield:Point(db.reverse and 'RIGHT' or 'LEFT')
	castbar.Shield:Point('BOTTOM')
	castbar.Shield:Point('TOP')

	UF:Update_StatusBar(castbar.Shield)

	if db.spark then
		castbar.Spark = castbar.Spark_
		castbar.Spark:ClearAllPoints()
		castbar.Spark:Point(db.reverse and 'LEFT' or 'RIGHT', barTexture)
		castbar.Spark:Point('BOTTOM')
		castbar.Spark:Point('TOP')
	elseif castbar.Spark then
		castbar.Spark:Hide()
		castbar.Spark = nil
	end

	local height
	if db.overlayOnFrame == 'None' then
		height = db.height

		if db.positionsGroup then
			castbar.Holder:ClearAllPoints()
			castbar.Holder:Point(E.InverseAnchors[db.positionsGroup.anchorPoint], frame, db.positionsGroup.anchorPoint, db.positionsGroup.xOffset, db.positionsGroup.yOffset)
		end

		local iconWidth = db.icon and db.iconAttached and (height + UF.BORDER) or SPACING1
		if frame.ORIENTATION == 'RIGHT' then
			castbar:Point('BOTTOMRIGHT', castbar.Holder, -iconWidth, SPACING1)
		else
			castbar:Point('BOTTOMLEFT', castbar.Holder, iconWidth, SPACING1)
		end

		castbar:Size(db.width - iconWidth - SPACING1, db.height - SPACING2)
	else
		local anchor = frame[db.overlayOnFrame]
		height = anchor:GetHeight()

		if not db.iconAttached then
			castbar:SetAllPoints(anchor)
		else
			local iconWidth = db.icon and (height + SPACING2 - 1) or 0
			if frame.ORIENTATION == 'RIGHT' then
				castbar:Point('TOPLEFT', anchor, 'TOPLEFT')
				castbar:Point('BOTTOMRIGHT', anchor, 'BOTTOMRIGHT', -iconWidth, 0)
			else
				castbar:Point('TOPLEFT', anchor, 'TOPLEFT', iconWidth, 0)
				castbar:Point('BOTTOMRIGHT', anchor, 'BOTTOMRIGHT')
			end
		end

		castbar:Size(db.width - SPACING2, db.height - SPACING2)
	end

	--Icon
	if db.icon then
		castbar.Icon = castbar.ButtonIcon
		castbar.Icon:SetTexCoords()

		if db.overlayOnFrame == 'None' then
			castbar.Icon.bg:Size(db.iconAttached and (height - UF.SPACING*2) or db.iconSize)
		else
			castbar.Icon.bg:Size(db.iconAttached and (height + UF.BORDER*2) or db.iconSize)
		end

		castbar.Icon.bg:ClearAllPoints()
		castbar.Icon.bg:Show()

		if not db.iconAttached then
			local attachPoint = db.iconAttachedTo == 'Frame' and frame or frame.Castbar
			castbar.Icon.bg:Point(E.InverseAnchors[db.iconPosition], attachPoint, db.iconPosition, db.iconXOffset, db.iconYOffset)
		elseif frame.ORIENTATION == 'RIGHT' then
			castbar.Icon.bg:Point('LEFT', castbar, 'RIGHT', (UF.thinBorders and 0 or 3), 0)
		else
			castbar.Icon.bg:Point('RIGHT', castbar, 'LEFT', -(UF.thinBorders and 0 or 3), 0)
		end
	else
		castbar.ButtonIcon.bg:Hide()
		castbar.Icon = nil
	end

	castbar.Text:SetAlpha(db.hideName and 0 or 1)
	castbar.Time:SetAlpha(db.hideTime and 0 or 1)

	if db.ticks then --Only player unitframe has this
		--Set tick width and color
		castbar.tickWidth = db.tickWidth
		castbar.tickColor = db.tickColor

		for _, tick in next, castbar.TickLines do
			tick:SetVertexColor(castbar.tickColor.r, castbar.tickColor.g, castbar.tickColor.b, castbar.tickColor.a)
			tick:Width(castbar.tickWidth)
		end
	end

	local customColor = db.customColor
	if customColor and customColor.enable then
		E:UpdateClassColor(customColor.color)
		E:UpdateClassColor(customColor.colorNoInterrupt)
		E:UpdateClassColor(customColor.colorInterrupted)

		if E.Retail then
			castbar.Shield:SetVertexColor(customColor.colorNoInterrupt.r, customColor.colorNoInterrupt.g, customColor.colorNoInterrupt.b, customColor.colorNoInterrupt.a)
			castbar.Shield.alphaValue = customColor.colorNoInterrupt.a
		end

		castbar.custom_backdrop = customColor.useCustomBackdrop and E:UpdateClassColor(customColor.colorBackdrop)
		UF:ToggleTransparentStatusBar(customColor.transparent, castbar, castbar.bg, nil, customColor.invertColors, db.reverse)
	else
		castbar.custom_backdrop = UF.db.colors.customcastbarbackdrop and E:UpdateClassColor(UF.db.colors.castbar_backdrop)
		UF:ToggleTransparentStatusBar(UF.db.colors.transparentCastbar, castbar, castbar.bg, nil, UF.db.colors.invertCastbar, db.reverse)

		if E.Retail then
			castbar.Shield:SetVertexColor(UF.db.colors.castNoInterrupt.r, UF.db.colors.castNoInterrupt.g, UF.db.colors.castNoInterrupt.b, UF.db.colors.castNoInterrupt.a)
			castbar.Shield.alphaValue = UF.db.colors.castNoInterrupt.a
		end
	end

	if castbar.Holder.mover then
		if db.overlayOnFrame ~= 'None' or not db.enable then
			E:DisableMover(castbar.Holder.mover.name)
		else
			E:EnableMover(castbar.Holder.mover.name)
		end
	end

	if db.enable and not frame:IsElementEnabled('Castbar') then
		frame:EnableElement('Castbar')
	elseif not db.enable and frame:IsElementEnabled('Castbar') then
		frame:DisableElement('Castbar')
	end
end

function UF:CustomCastDelayText(duration, durationObject)
	if durationObject then
		local remain = durationObject:GetRemainingDuration()
		self.Time:SetFormattedText('%.1f', remain)

		return
	elseif not duration then
		return
	end

	local db = self:GetParent().db
	if not (db and db.castbar) then return end
	db = db.castbar.format

	if self.channeling then
		if db == 'CURRENT' then
			self.Time:SetFormattedText('%.1f |cffaf5050%.1f|r', abs(duration - self.max), self.delay)
		elseif db == 'CURRENTMAX' then
			self.Time:SetFormattedText('%.1f / %.1f |cffaf5050%.1f|r', duration, self.max, self.delay)
		elseif db == 'REMAINING' then
			self.Time:SetFormattedText('%.1f |cffaf5050%.1f|r', duration, self.delay)
		elseif db == 'REMAININGMAX' then
			self.Time:SetFormattedText('%.1f / %.1f |cffaf5050%.1f|r', abs(duration - self.max), self.max, self.delay)
		end
	else
		if db == 'CURRENT' then
			self.Time:SetFormattedText('%.1f |cffaf5050%s %.1f|r', duration, '+', self.delay)
		elseif db == 'CURRENTMAX' then
			self.Time:SetFormattedText('%.1f / %.1f |cffaf5050%s %.1f|r', duration, self.max, '+', self.delay)
		elseif db == 'REMAINING' then
			self.Time:SetFormattedText('%.1f |cffaf5050%s %.1f|r', abs(duration - self.max), '+', self.delay)
		elseif db == 'REMAININGMAX' then
			self.Time:SetFormattedText('%.1f / %.1f |cffaf5050%s %.1f|r', abs(duration - self.max), self.max, '+', self.delay)
		end
	end
end

function UF:CustomTimeText(duration, durationObject)
	if durationObject then
		local remain = durationObject:GetRemainingDuration()
		self.Time:SetFormattedText('%.1f', remain)

		return
	elseif not duration then
		return
	end

	local db = self:GetParent().db
	if not (db and db.castbar) then return end
	db = db.castbar.format

	if self.channeling then
		if db == 'CURRENT' then
			self.Time:SetFormattedText('%.1f', abs(duration - self.max))
		elseif db == 'CURRENTMAX' then
			self.Time:SetFormattedText('%.1f / %.1f', abs(duration - self.max), self.max)
		elseif db == 'REMAINING' then
			self.Time:SetFormattedText('%.1f', duration)
		elseif db == 'REMAININGMAX' then
			self.Time:SetFormattedText('%.1f / %.1f', duration, self.max)
		end
	else
		if db == 'CURRENT' then
			self.Time:SetFormattedText('%.1f', duration)
		elseif db == 'CURRENTMAX' then
			self.Time:SetFormattedText('%.1f / %.1f', duration, self.max)
		elseif db == 'REMAINING' then
			self.Time:SetFormattedText('%.1f', abs(duration - self.max))
		elseif db == 'REMAININGMAX' then
			self.Time:SetFormattedText('%.1f / %.1f', abs(duration - self.max), self.max)
		end
	end
end

function UF:HideTicks(frame)
	for _, tick in next, frame.TickLines do
		tick:Hide()
	end
end

function UF:SetCastTicks(frame, numTicks)
	UF:HideTicks(frame)

	if numTicks and numTicks <= 0 then return end

	local offset = frame:GetWidth() / numTicks

	for i = 1, numTicks - 1 do
		local tick = frame.TickLines[i]
		if not tick then
			tick = frame:CreateTexture(nil, 'OVERLAY', nil, 4)
			tick:SetTexture(E.media.blankTex)
			tick:Width(frame.tickWidth)
			frame.TickLines[i] = tick
		end

		tick:SetVertexColor(frame.tickColor.r, frame.tickColor.g, frame.tickColor.b, frame.tickColor.a)
		tick:ClearAllPoints()
		tick:Point('TOP')
		tick:Point('BOTTOM')
		tick:Point('RIGHT', frame, 'LEFT', offset * i, 0)
		tick:Show()
	end
end

function UF:GetInterruptColor(db, unit)
	local colors = ElvUF.colors
	local customColor = db and db.castbar and db.castbar.customColor
	local custom, r, g, b = customColor and customColor.enable and customColor, colors.castColor.r, colors.castColor.g, colors.castColor.b

	local notInterruptible = E:NotSecretValue(self.notInterruptible) and self.notInterruptible
	if notInterruptible and (UnitIsPlayer(unit) or (unit ~= 'player' and UnitCanAttack('player', unit))) then
		if custom and custom.colorNoInterrupt then
			return custom.colorNoInterrupt.r, custom.colorNoInterrupt.g, custom.colorNoInterrupt.b
		else
			return colors.castNoInterrupt.r, colors.castNoInterrupt.g, colors.castNoInterrupt.b
		end
	elseif ((custom and custom.useClassColor) or (not custom and UF.db.colors.castClassColor)) and UnitIsPlayer(unit) then
		local _, Class = UnitClass(unit)
		local t = Class and colors.class[Class]
		if t then return t.r, t.g, t.b end
	elseif (custom and custom.useReactionColor) or (not custom and UF.db.colors.castReactionColor) then
		local Reaction = UnitReaction(unit, 'player')
		local t = Reaction and colors.reaction[Reaction]
		if t then return t.r, t.g, t.b end
	elseif custom then
		return customColor.color.r, customColor.color.g, customColor.color.b
	end

	return r, g, b
end

function UF:GetCasterColor(unit)
	if not unit then return end

	local _, className = UnitClass(unit)
	local classColor = className and E:ClassColor(className)
	if classColor then
		return classColor.colorStr
	end
end

function UF:SetCastText(castbar, db, changed, spellName, targetName)
	if E:NotSecretValue(targetName) and targetName then
		local color = db.displayTargetClass and UF:GetCasterColor(targetName)
		castbar.Text:SetFormattedText('%s: |c%s%s|r', spellName, color or 'FFdddddd', targetName)
	elseif changed then -- always true when secret
		castbar.Text:SetText(spellName)
	end
end

function UF:PostCastStart(unit)
	local parent = self.__owner
	local db = parent and parent.db
	if not db or not db.castbar then return end

	if unit == 'vehicle' then unit = 'player' end

	self.unit = unit

	if E:IsSecretValue(self.spellID) then
		UF:SetCastText(self, db.castbar, true, self.spellName, self.curTarget)

		if self.channeling and db.castbar.ticks and parent.unitframeType == 'player' then
			UF:HideTicks(self)
		end
	else
		local spellName = self.spellName
		local length = db.castbar.nameLength
		local name = (length and length > 0 and utf8sub(spellName, 1, length)) or spellName
		local changed = name ~= spellName

		if db.castbar.displayTarget then -- player or NPCs; if used on other players: the cast target doesn't match their target, can be misleading if they mouseover cast
			if parent.unitframeType == 'player' then
				UF:SetCastText(self, db.castbar, changed, name, self.curTarget)
			elseif parent.unitframeType == 'pet' or parent.unitframeType == 'boss' then
				if self.curTarget then
					UF:SetCastText(self, db.castbar, changed, name, self.curTarget)
				else
					local unitName = UnitName(unit)
					local targetName = UnitName(unit..'target')
					local allowText = E:NotSecretValue(targetName) and (targetName and targetName ~= '') and (E:NotSecretValue(unitName) and (targetName ~= unitName))
					UF:SetCastText(self, db.castbar, changed, name, allowText and targetName)
				end
			end
		elseif changed then
			self.Text:SetText(name)
		end

		if self.channeling and db.castbar.ticks and parent.unitframeType == 'player' then
			local spellID = E:NotSecretValue(self.spellID) and self.spellID or nil

			local global = E.global.unitframe
			local baseTicks = global.ChannelTicks[spellID]

			-- Separate group, so they can be effected by haste or size if needed
			local talentTicks = baseTicks and global.TalentChannelTicks[spellID]
			if talentTicks then
				for auraID, tickCount in next, talentTicks do
					if IsSpellInSpellBook(auraID, nil, true) or IsSpellKnown(auraID) then
						baseTicks = tickCount
						break -- found one so stop
					end
				end
			end

			-- Base ticks upgraded by another aura
			local auraTicks = baseTicks and global.AuraChannelTicks[spellID]
			if auraTicks then
				for auraID, tickCount in next, auraTicks.spells do
					if E:GetAuraByID(unit, auraID, auraTicks.filter) then
						baseTicks = tickCount
						break -- found one so stop
					end
				end
			end

			-- Wait for chain to happen
			local chainTicks = baseTicks and global.ChainChannelTicks[spellID]
			if chainTicks then -- requires a window: ChainChannelTime
				local now = GetTime() -- this will clear old ones too
				local seconds = global.ChainChannelTime[spellID]
				local match = seconds and self.chainTime and self.chainTick == spellID

				if match and (now - seconds) < self.chainTime then
					baseTicks = baseTicks + chainTicks
				end

				self.chainTime = now
				self.chainTick = spellID
			else
				self.chainTick = nil -- not a chain spell
				self.chainTime = nil -- clear the time too
			end

			local hasteTicks = baseTicks and global.HastedChannelTicks[spellID]
			if hasteTicks then -- requires tickSize
				local haste = UnitSpellHaste('player') * 0.01
				local rate = 1 / baseTicks
				local first = rate * 0.5

				local bonus = 0
				if haste >= first then
					bonus = bonus + 1
				end

				local x = E:Round(first + rate, 2)
				while haste >= x do
					x = E:Round(first + (rate * bonus), 2)

					if haste >= x then
						bonus = bonus + 1
					end
				end

				UF:SetCastTicks(self, baseTicks + bonus)
				self.hadTicks = true
			elseif baseTicks then
				UF:SetCastTicks(self, baseTicks)
				self.hadTicks = true
			else
				UF:HideTicks(self)
			end
		end
	end

	if self.SafeZone then
		self.SafeZone:Show()
	end

	local r, g, b = UF.GetInterruptColor(self, db, unit)
	UF:SetStatusBarColor(self, r, g, b)
end

function UF:PostCastStop(unit)
	if self.hadTicks and unit == 'player' then
		UF:HideTicks(self)
		self.hadTicks = false
		self.chainTick = nil -- reset the chain
		self.chainTime = nil -- spell cast vars
	end
end

function UF:PostCastFail()
	local db = self:GetParent().db
	local customColor = db and db.castbar and db.castbar.customColor
	local color = (customColor and customColor.enable and customColor.colorInterrupted) or UF.db.colors.castInterruptedColor

	UF:SetStatusBarColor(self, color.r, color.g, color.b)

	if self.SafeZone then
		self.SafeZone:Hide()
	end
end

function UF:PostCastInterruptible(unit)
	if unit == 'vehicle' or unit == 'player' then return end

	local db = self:GetParent().db
	if not db or not db.castbar then return end

	local r, g, b = UF.GetInterruptColor(self, db, unit)
	UF:SetStatusBarColor(self, r, g, b)
end
