
---@type detailsframework
local detailsFramework = _G["DetailsFramework"]
if (not detailsFramework or not DetailsFrameworkCanLoad) then
	return
end

local _
local type = type
local floor = math.floor
local GetTime = GetTime

---@class df_timebar : statusbar, df_scripthookmixin, df_widgets
---@field type string
---@field dframework boolean
---@field statusBar df_timebar_statusbar
---@field widget statusbar
---@field GetUIObject fun(self:df_timebar):statusbar returns the UIObject (the underlying StatusBar) that is behind the wrapper table
---@field direction string
---@field HookList table
---@field tooltip string
---@field locked boolean
---@field HasTimer fun(self:df_timebar):boolean return if the timer bar is active showing a timer
---@field SetTimer fun(self:df_timebar, currentTime:number, startTime:number|boolean|nil, endTime:number|nil)
---@field StartTimer fun(self:df_timebar, currentTime:number, startTime:number, endTime:number)
---@field StopTimer fun(self:df_timebar)
---@field ShowSpark fun(self:df_timebar, state:boolean, alpha:number|nil, color:string|nil)
---@field ShowTimer fun(self:df_timebar, bShowTimer:boolean)
---@field SetIcon fun(self:df_timebar, texture:string, L:number|nil, R:number|nil, T:number|nil, B:number|nil)
---@field SetIconSize fun(self:df_timebar, width:number, height:number)
---@field SetTexture fun(self:df_timebar, texture:texturepath|textureid)
---@field SetColor fun(self:df_timebar, color:any, green:number|nil, blue:number|nil, alpha:number|nil)
---@field SetLeftText fun(self:df_timebar, text:string)
---@field SetRightText fun(self:df_timebar, text:string)
---@field SetFont fun(self:df_timebar, font:string|nil, size:number|nil, color:any, outline:string|boolean|nil, shadowColor:any, shadowX:number?, shadowY:number?)
---@field SetThrottle fun(self:df_timebar, seconds:number)
---@field SetDirection fun(self:df_timebar, direction:string)
---@field SetBackgroundColor fun(self:df_timebar, color:any, green:number|nil, blue:number|nil, alpha:number|nil)
---@field SetIconPosition fun(self:df_timebar, iconPosition:string?) DF_TIMEBAR_ICON_POSITIONS_OUTSIDE or DF_TIMEBAR_ICON_POSITIONS_INSIDE

---@class df_timebar_statusbar : statusbar
---@field MyObject df_timebar
---@field hasTimer boolean
---@field startTime number
---@field endTime number
---@field timeLeft1 number
---@field timeLeft2 number
---@field throttle number
---@field isUsingThrottle boolean
---@field showTimer boolean
---@field amountThrottle number
---@field sparkAlpha number
---@field sparkColorR number
---@field sparkColorG number
---@field sparkColorB number
---@field dontShowSpark boolean
---@field direction string
---@field spark texture
---@field icon texture
---@field leftText fontstring
---@field rightText fontstring
---@field backgroundTexture texture
---@field barTexture texture

local APITimeBarFunctions

do
	local metaPrototype = {
		WidgetType = "timebar",
		dversion = detailsFramework.dversion,
	}

	--check if there's a metaPrototype already existing
	if (_G[detailsFramework.GlobalWidgetControlNames["timebar"]]) then
		--get the already existing metaPrototype
		local oldMetaPrototype = _G[detailsFramework.GlobalWidgetControlNames["timebar"]]
		--check if is older
		if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < detailsFramework.dversion) ) then
			--the version is older them the currently loading one
			--copy the new values into the old metatable
			for funcName, _ in pairs(metaPrototype) do
				oldMetaPrototype[funcName] = metaPrototype[funcName]
			end
		end
	else
		--first time loading the framework
		_G[detailsFramework.GlobalWidgetControlNames["timebar"]] = metaPrototype
	end
end

local TimeBarMetaFunctions = _G[detailsFramework.GlobalWidgetControlNames["timebar"]]
detailsFramework:Mixin(TimeBarMetaFunctions, detailsFramework.ScriptHookMixin)

--methods
TimeBarMetaFunctions.SetMembers = TimeBarMetaFunctions.SetMembers or {}
TimeBarMetaFunctions.GetMembers = TimeBarMetaFunctions.GetMembers or {}

TimeBarMetaFunctions.__index = function(table, key)
    local func = TimeBarMetaFunctions.GetMembers[key]
    if (func) then
        return func(table, key)
    end

    local fromMe = rawget(table, key)
    if (fromMe) then
        return fromMe
    end

    return TimeBarMetaFunctions[key]
end

TimeBarMetaFunctions.__newindex = function(table, key, value)
    local func = TimeBarMetaFunctions.SetMembers[key]
    if (func) then
        return func(table, value)
    else
        return rawset(table, key, value)
    end
end

--scripts
local OnEnterFunc = function(statusBar)
    local kill = statusBar.MyObject:RunHooksForWidget("OnEnter", statusBar, statusBar.MyObject)
    if (kill) then
        return
    end

    if (statusBar.MyObject.tooltip) then
        GameCooltip2:Reset()
        GameCooltip2:AddLine(statusBar.MyObject.tooltip)
        GameCooltip2:ShowCooltip(statusBar, "tooltip")
    end
end

local OnLeaveFunc = function(statusBar)
    local kill = statusBar.MyObject:RunHooksForWidget("OnLeave", statusBar, statusBar.MyObject)
    if (kill) then
        return
    end

    if (statusBar.MyObject.tooltip) then
        GameCooltip2:Hide()
    end
end

local OnHideFunc = function(statusBar)
    local kill = statusBar.MyObject:RunHooksForWidget("OnHide", statusBar, statusBar.MyObject)
    if (kill) then
        return
    end
end

local OnShowFunc = function(statusBar)
    local kill = statusBar.MyObject:RunHooksForWidget("OnShow", statusBar, statusBar.MyObject)
    if (kill) then
        return
    end
end

local OnMouseDownFunc = function(statusBar, mouseButton)
    local kill = statusBar.MyObject:RunHooksForWidget("OnMouseDown", statusBar, statusBar.MyObject)
    if (kill) then
        return
    end
end

local OnMouseUpFunc = function(statusBar, mouseButton)
    local kill = statusBar.MyObject:RunHooksForWidget("OnMouseUp", statusBar, statusBar.MyObject)
    if (kill) then
        return
    end
end

---return the UIObject (the underlying StatusBar frame) that is behind the wrapper table.
function TimeBarMetaFunctions:GetUIObject()
    return self.widget
end

--timer functions
function TimeBarMetaFunctions:SetIconSize(width, height)
    if (width and not height) then
        self.statusBar.icon:SetWidth(width)

    elseif (not width and height) then
        self.statusBar.icon:SetHeight(height)

    elseif (width and height) then
        self.statusBar.icon:SetSize(width, height)
    end
end

function TimeBarMetaFunctions:SetIcon(texture, L, R, T, B)
    if (texture) then
        self.statusBar.icon:Show()
        self.statusBar.icon:SetTexture(texture)
        self:SetIconPosition()

        if (L) then
            self.statusBar.icon:SetTexCoord(L, R, T, B)
        end
    else
        self.statusBar.icon:Hide()
        self.statusBar.leftText:ClearAllPoints()
        self.statusBar.leftText:SetPoint("left", self.statusBar, "left", 2, 0)
    end
end

function TimeBarMetaFunctions:GetIcon()
    return self.statusBar.icon
end

function TimeBarMetaFunctions:SetTexture(texture)
    self.statusBar.barTexture:SetTexture(texture)
end

function TimeBarMetaFunctions:SetColor(color, green, blue, alpha)
    local r, g, b, a = detailsFramework:ParseColors(color, green, blue, alpha)
    self.statusBar.barTexture:SetVertexColor(r, g, b, a)
end

function TimeBarMetaFunctions:SetLeftText(text)
    self.statusBar.leftText:SetText(text)
end
function TimeBarMetaFunctions:SetRightText(text)
    self.statusBar.rightText:SetText(text)
end

DF_TIMEBAR_ICON_POSITIONS_OUTSIDE = "outside"
DF_TIMEBAR_ICON_POSITIONS_INSIDE = "inside"

function TimeBarMetaFunctions:SetIconPosition(iconPosition)
    if not iconPosition then
        iconPosition = self.iconPosition
    end

    self.statusBar.icon:ClearAllPoints()

    if iconPosition == DF_TIMEBAR_ICON_POSITIONS_INSIDE then
        self.statusBar.icon:SetPoint("left", self.statusBar, "left", 2, 0)
        self.statusBar.leftText:ClearAllPoints()
        self.statusBar.leftText:SetPoint("left", self.statusBar.icon, "right", 2, 0)

    elseif iconPosition == DF_TIMEBAR_ICON_POSITIONS_OUTSIDE then
        self.statusBar.icon:SetPoint("topright", self.statusBar, "topleft", 0, 0)
        self.statusBar.icon:SetPoint("bottomright", self.statusBar, "bottomleft", 0, 0)
        self.statusBar.icon:SetSize(self.statusBar:GetHeight()-2, self.statusBar:GetHeight()-2)
        self.statusBar.leftText:ClearAllPoints()
        self.statusBar.leftText:SetPoint("left", self.statusBar, "left", 2, 0)
    end
end

function TimeBarMetaFunctions:SetFont(font, size, color, outline, shadowColor, shadowX, shadowY)
    if (font) then
        detailsFramework:SetFontFace(self.statusBar.leftText, font)
        detailsFramework:SetFontFace(self.statusBar.rightText, font)
    end

    if (size) then
        detailsFramework:SetFontSize(self.statusBar.leftText, size)
        detailsFramework:SetFontSize(self.statusBar.rightText, size)
    end

    if (color) then
        detailsFramework:SetFontColor(self.statusBar.leftText, color)
        detailsFramework:SetFontColor(self.statusBar.rightText, color)
    end

    if (outline) then
        detailsFramework:SetFontOutline(self.statusBar.leftText, outline)
        detailsFramework:SetFontOutline(self.statusBar.rightText, outline)
    end

    if (shadowColor) then
        self.statusBar.leftText:SetShadowColor(detailsFramework:ParseColors(shadowColor))
        self.statusBar.rightText:SetShadowColor(detailsFramework:ParseColors(shadowColor))
    end

    if (shadowX and shadowY) then
        self.statusBar.leftText:SetShadowOffset(shadowX, shadowY)
        self.statusBar.rightText:SetShadowOffset(shadowX, shadowY)
    end
end

--set background texture color
function TimeBarMetaFunctions:SetBackgroundColor(color, green, blue, alpha)
    local r, g, b, a = detailsFramework:ParseColors(color, green, blue, alpha)
    self.statusBar.backgroundTexture:SetVertexColor(r, g, b, a)
end

---set a throttle for the timer bar, the timer will only update every X seconds
---calling without parameters will disable the throttle
---@param seconds number|nil the amount of seconds to throttle the timer
function TimeBarMetaFunctions:SetThrottle(seconds)
    if (seconds and seconds > 0) then
        self.statusBar.isUsingThrottle = true
        self.statusBar.amountThrottle = seconds
    else
        self.statusBar.isUsingThrottle = false
    end
end

---accept 'left' 'right' or nil, if ommited will default to right
---@param direction "left"|"right"|nil the direction of the timer bar
function TimeBarMetaFunctions:SetDirection(direction)
    direction = direction or "right"
    self.direction = direction
end

function TimeBarMetaFunctions:HasTimer()
    return self.statusBar.hasTimer
end

function TimeBarMetaFunctions:StopTimer()
    if (self.statusBar.hasTimer) then
        self.statusBar.hasTimer = nil
        local kill = self:RunHooksForWidget("OnTimerEnd", self.statusBar, self)
        if (kill) then
            return
        end
    end

    local statusBar = self.statusBar
    statusBar:SetScript("OnUpdate", nil)

    statusBar:SetMinMaxValues(0, 100)
    statusBar:SetValue(100)
    statusBar.rightText:SetText("")

    statusBar.spark:Hide()
end

function TimeBarMetaFunctions:ShowTimer(bShowTimer)
    if (bShowTimer) then
        self.statusBar.showTimer = true
    else
        self.statusBar.showTimer = nil
    end
end

function TimeBarMetaFunctions:ShowSpark(state, alpha, color)
    if (type(state) == "boolean" and state == false) then
        self.statusBar.dontShowSpark = true
    else
        self.statusBar.dontShowSpark = nil
    end

    if (alpha) then
        self.statusBar.sparkAlpha = alpha
    else
        self.statusBar.sparkAlpha = nil
    end

    if (color) then
        local r, g, b = detailsFramework:ParseColors(color)
        if (r and g and b) then
            self.statusBar.sparkColorR = r
            self.statusBar.sparkColorG = g
            self.statusBar.sparkColorB = b
        end
    else
        self.statusBar.sparkColorR = nil
        self.statusBar.sparkColorG = nil
        self.statusBar.sparkColorB = nil
    end
end

---@param self df_timebar_statusbar
---@param deltaTime number
local OnUpdateFunc = function(self, deltaTime)
    if (self.isUsingThrottle) then
        self.throttle = self.throttle + deltaTime
        if (self.throttle < self.amountThrottle) then
            return
        end
        self.throttle = 0
    end

    local timeNow = GetTime()
    self:SetValue(timeNow)

    --adjust the spark
    local spark = self.spark
    local startTime, endTime = self:GetMinMaxValues()

    if (not self.dontShowSpark) then
        if (endTime - startTime > 0) then
            local pct = abs((timeNow - endTime) / (endTime - startTime))
            pct = abs(1 - pct)
            local sparkOffset = -16

            if (self.direction ~= "right") then
                --moving from right to left, so invert the pct
                pct = abs(1 - pct)
                sparkOffset = -14
            end

            spark:SetPoint("left", self, "left", (self:GetWidth() * pct) + sparkOffset, 0)
            spark:Show()
        else
            spark:Hide()
        end
    end

    if (self.showTimer) then
        local timeLeft = floor(endTime - timeNow) + 1
        local formatedTimeLeft = detailsFramework:IntegerToTimer(timeLeft)
        self.rightText:SetText(formatedTimeLeft)
    end

    --check if finished
    if (timeNow >= self.endTime) then
        self.MyObject:StopTimer()
    end

    self.MyObject:RunHooksForWidget("OnUpdate", self, self, deltaTime)
end

---start a timer on the timebar
---calling without parameters will stop the timer
---@param self df_timebar
---@param currentTime number the time in seconds if startTime is a boolean true. GetTime() when start and end time are passed
---@param startTime number|boolean|nil GetTime() when the timer started. if passed true: startTime and endTime are GetTime() and GetTime() + currentTime, currenTime is the time in seconds
---@param endTime number|nil GetTime() when the timer will end. ignored if startTime is a boolean true
function TimeBarMetaFunctions:SetTimer(currentTime, startTime, endTime)
    self.statusBar:Show()

    if (not currentTime or currentTime == 0) then
        self:StopTimer()
        return
    end

    if (startTime and endTime) then
        if (self.statusBar.hasTimer and currentTime == self.statusBar.timeLeft1) then
            --it is the same timer called again
            return
        end
        self.statusBar.startTime = tonumber(startTime) or 0 --fit the number type
        self.statusBar.endTime = endTime
    else
        local bForceNewTimer = type(startTime) == "boolean" and startTime
        if (self.statusBar.hasTimer and currentTime == self.statusBar.timeLeft2 and not bForceNewTimer) then
            --it is the same timer called again
            return
        end
        self.statusBar.startTime = GetTime()
        self.statusBar.endTime = GetTime() + currentTime
        self.statusBar.timeLeft2 = currentTime
    end

    self.statusBar:SetMinMaxValues(self.statusBar.startTime, self.statusBar.endTime)

    if (self.direction == "right") then
        self.statusBar:SetReverseFill(false)
    else
        self.statusBar:SetReverseFill(true)
    end

    if (self.statusBar.dontShowSpark) then
        self.statusBar.spark:Hide()
    else
        self.statusBar.spark:Show()
        self.statusBar.spark:SetHeight(self.statusBar:GetHeight()+26)

        if (self.statusBar.sparkAlpha) then
            self.statusBar.spark:SetAlpha(self.statusBar.sparkAlpha)
        else
            self.statusBar.spark:SetAlpha(1)
        end

        if (self.statusBar.sparkColorR) then
            self.statusBar.spark:SetVertexColor(self.statusBar.sparkColorR, self.statusBar.sparkColorG, self.statusBar.sparkColorB)
        else
            self.statusBar.spark:SetVertexColor(1, 1, 1)
        end
    end

    self.statusBar.hasTimer = true
    self.statusBar.direction = self.direction
    self.statusBar.throttle = 0

    self.statusBar:SetScript("OnUpdate", OnUpdateFunc)

    local kill = self:RunHooksForWidget("OnTimerStart", self.statusBar, self)
    if (kill) then
        return
    end
end

---create a time bar widget, a timebar is a statubar that can have a timer and a spark
---@param parent frame the parent frame
---create a time bar (a StatusBar with built-in timer animation, icon, two text labels, and spark).
---This function returns a wrapper Lua table (df_timebar), NOT a Blizzard frame. The underlying
---UIObject (the Blizzard StatusBar frame) is at `wrapper.widget` (or equivalently
---`wrapper.statusBar`) and via `wrapper:GetUIObject()`. Method calls on the wrapper itself are fine
---(the metatable forwards them), but when the wrapper is passed AS AN ARGUMENT to a Blizzard API
---that expects a frame — SetPoint relative anchor, CreateFrame parent, GameTooltip:SetOwner,
---secure-template ref, etc. — it MUST be unwrapped via `wrapper:GetUIObject()` first, otherwise
---the C side will error or misbehave because the wrapper has no frame userdata.
---@param texture texturepath|textureid the texture of the bar
---@param width number? the width of the bar, default is 150
---@param height number? the height of the bar, default is 20
---@param value number? the initial value of the bar, default is 0
---@param member string? the name of the member in the parent frame
---@param name string? the name of the widget
---@return df_timebar
function detailsFramework:CreateTimeBar(parent, texture, width, height, value, member, name)
    if (not name) then
		name = "DetailsFrameworkBarNumber" .. detailsFramework.BarNameCounter
		detailsFramework.BarNameCounter = detailsFramework.BarNameCounter + 1

	elseif (not parent) then
		error("Details! FrameWork: parent not found.", 2)
	end

	if (name:find("$parent")) then
		local parentName = detailsFramework:GetParentName(parent)
		name = name:gsub("$parent", parentName)
	end

	local timeBar = {
        type = "timebar",
        dframework = true
    }

	if (member) then
		parent[member] = timeBar
	end

    ---@diagnostic disable-next-line: undefined-field
	if (parent.dframework) then
    ---@diagnostic disable-next-line: undefined-field
		parent = parent.widget
	end

	value = value or 0
	width = width or 150
	height = height or 20
	timeBar.locked = false

    timeBar.statusBar = CreateFrame("statusbar", name, parent, "BackdropTemplate")
    timeBar.widget = timeBar.statusBar
    detailsFramework:Mixin(timeBar.statusBar, detailsFramework.WidgetFunctions)
    timeBar.statusBar.MyObject = timeBar
    timeBar.direction = "right"
    timeBar.iconPosition = DF_TIMEBAR_ICON_POSITIONS_INSIDE

    if (not APITimeBarFunctions) then
        APITimeBarFunctions = true
        local idx = getmetatable(timeBar.statusBar).__index
        for funcName, funcAddress in pairs(idx) do
            if (not TimeBarMetaFunctions[funcName]) then
                TimeBarMetaFunctions[funcName] = function(object, ...)
                    local x = loadstring("return _G['"..object.statusBar:GetName().."']:"..funcName.."(...)")
                    if (x) then
                        return x(...)
                    end
                end
            end
        end
    end

    --create widgets
        timeBar.statusBar:SetWidth(width)
		timeBar.statusBar:SetHeight(height)
		timeBar.statusBar:SetFrameLevel(parent:GetFrameLevel()+1)
		timeBar.statusBar:SetMinMaxValues(0, 100)
		timeBar.statusBar:SetValue(value or 100)
		timeBar.statusBar:EnableMouse(false)

        timeBar.statusBar.backgroundTexture = timeBar.statusBar:CreateTexture(nil, "border")
        timeBar.statusBar.backgroundTexture:SetColorTexture(.9, .9, .9, 1)
        timeBar.statusBar.backgroundTexture:SetVertexColor(.1, .1, .1, .6)
        timeBar.statusBar.backgroundTexture:SetAllPoints()

        timeBar.statusBar.barTexture = timeBar.statusBar:CreateTexture(nil, "artwork")
        timeBar.statusBar.barTexture:SetTexture(texture or [[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]])
        timeBar.statusBar:SetStatusBarTexture(timeBar.statusBar.barTexture)

        timeBar.statusBar.spark = timeBar.statusBar:CreateTexture(nil, "overlay", nil, 7)
        timeBar.statusBar.spark:SetTexture([[Interface\CastingBar\UI-CastingBar-Spark]])
        timeBar.statusBar.spark:SetBlendMode("ADD")
        timeBar.statusBar.spark:Hide()

        timeBar.statusBar.icon = timeBar.statusBar:CreateTexture(nil, "overlay", nil, 5)
        timeBar.statusBar.icon:SetPoint("left", timeBar.statusBar, "left", 2, 0)
        timeBar.statusBar.icon:SetSize(height - 2, height - 2)

        timeBar.statusBar.leftText = timeBar.statusBar:CreateFontString("$parentLeftText", "overlay", "GameFontNormal", 4)
        timeBar.statusBar.leftText:SetPoint("left", timeBar.statusBar.icon, "right", 2, 0)

        timeBar.statusBar.rightText = timeBar.statusBar:CreateFontString(nil, "overlay", "GameFontNormal", 4)
        timeBar.statusBar.rightText:SetPoint("right", timeBar.statusBar, "right", -2, 0)
        timeBar.statusBar.rightText:SetJustifyH("left")

	--hooks
		timeBar.HookList = {
			OnEnter = {},
			OnLeave = {},
			OnHide = {},
			OnShow = {},
            OnUpdate = {},
			OnMouseDown = {},
            OnMouseUp = {},
            OnTimerStart = {},
			OnTimerEnd = {},
		}

		timeBar.statusBar:SetScript("OnEnter", OnEnterFunc)
		timeBar.statusBar:SetScript("OnLeave", OnLeaveFunc)
		timeBar.statusBar:SetScript("OnHide", OnHideFunc)
		timeBar.statusBar:SetScript("OnShow", OnShowFunc)
		timeBar.statusBar:SetScript("OnMouseDown", OnMouseDownFunc)
		timeBar.statusBar:SetScript("OnMouseUp", OnMouseUpFunc)

	--set class
	setmetatable(timeBar, TimeBarMetaFunctions)

	return timeBar
end
