
local detailsFramework = DetailsFramework

if (not detailsFramework or not DetailsFrameworkCanLoad) then
	return
end

local unpack = unpack
local CreateFrame = CreateFrame
local geterrorhandler = geterrorhandler
local wipe = wipe

--definitions

---@class df_headercolumndata : {key: string, name: string, icon: string, texcoord: table, text: string, canSort: boolean, selected: boolean, width: number, height: number, align: string, offset: number}

---@class df_headerchild : uiobject
---@field FramesToAlign table

---@class df_headerframe : frame, df_headermixin, df_optionsmixin
---@field columnHeadersCreated df_headercolumnframe[]
---@field options table
---@field HeaderTable df_headercolumndata[]
---@field columnSelected number

---@class df_headermixin : table
---@field NextHeader number
---@field HeaderWidth number
---@field HeaderHeight number
---@field OnColumnSettingChangeCallback function
---@field GetColumnWidth fun(self: df_headerframe, columnId: number) : number
---@field SetHeaderTable fun(self: df_headerframe, table)
---@field GetSelectedColumn fun(self: df_headerframe) : number, string, string, string
---@field Refresh fun(self: df_headerframe)
---@field UpdateSortArrow fun(self: df_headerframe, columnHeader: df_headercolumnframe, defaultShown: boolean|nil, defaultOrder: string|nil)
---@field UpdateColumnHeader fun(self: df_headerframe, columnHeader: df_headercolumnframe, headerIndex)
---@field ResetColumnHeaderBackdrop fun(self: df_headerframe, columnHeader: df_headercolumnframe)
---@field SetBackdropColorForSelectedColumnHeader fun(self: df_headerframe, columnHeader: df_headercolumnframe)
---@field ClearColumnHeader fun(self: df_headerframe, columnHeader: df_headercolumnframe)
---@field GetNextHeader fun(self: df_headerframe) : df_headercolumnframe
---@field SetColumnSettingChangedCallback fun(self: df_headerframe, func: function) : boolean

---@class df_headercolumnframe : button
---@field Icon texture
---@field Text fontstring
---@field Arrow texture
---@field Separator texture
---@field resizerButton df_headerresizer
---@field bIsRezising boolean
---@field bInUse boolean
---@field columnData table
---@field order string
---@field columnIndex number
---@field columnAlign string
---@field XPosition number
---@field columnOffset number
---@field key string used to sort the values

---@class df_headerresizer : button
---@field texture texture

--mixed functions
---@class df_headerfunctions : table
detailsFramework.HeaderFunctions = {
    ---comment
    ---@param self df_headerchild
    ---@param frame uiobject
	AddFrameToHeaderAlignment = function(self, frame)
		self.FramesToAlign = self.FramesToAlign or {}
		table.insert(self.FramesToAlign, frame)
	end,

    ---comment
    ---@param self df_headerchild
	ResetFramesToHeaderAlignment = function(self)
		wipe(self.FramesToAlign)
	end,

	SetFramesToHeaderAlignment = function(self, ...)
        ---@cast self df_headerchild
		wipe(self.FramesToAlign)
		self.FramesToAlign = {...}
	end,

	GetFramesFromHeaderAlignment = function(self, frame)
		return self.FramesToAlign or {}
	end,

	---@param self uiobject
	---@param headerFrame df_headerframe
	---@param anchor string
	AlignWithHeader = function(self, headerFrame, anchor)
		local columnHeaderFrames = headerFrame.columnHeadersCreated
		anchor = anchor or "topleft"

        ---@cast self df_headerchild

		for i = 1, #self.FramesToAlign do
			---@type uiobject
			local uiObject = self.FramesToAlign[i]
			uiObject:ClearAllPoints()

			---@type df_headercolumnframe
			local columnHeader = columnHeaderFrames[i]
			if (columnHeader) then
				local offset = 0

				if (columnHeader.columnAlign == "right") then
					offset = columnHeader:GetWidth()
				end

				if (uiObject:GetObjectType() == "FontString") then
					---@cast uiObject fontstring
					if (columnHeader.columnAlign == "right") then
						uiObject:SetJustifyH("right")
					elseif (columnHeader.columnAlign == "left") then
						uiObject:SetJustifyH("left")
					elseif (columnHeader.columnAlign == "center") then
						uiObject:SetJustifyH("center")
					end
				end

				uiObject:SetPoint(columnHeader.columnAlign, self, anchor, columnHeader.XPosition + columnHeader.columnOffset + offset, 0)
			end
		end
	end,

	---comment
	---@param columnHeader df_headercolumnframe
	---@param buttonClicked string
	OnClick = function(columnHeader, buttonClicked)
		--get the header main frame
		local headerFrame = columnHeader:GetParent()
		---@cast headerFrame df_headerframe

		--if this header does not have a clickable header, just ignore
		if (not headerFrame.columnSelected) then
			return
		end

		--check if this column has 'canSort' key, otherwise ignore the click
		if (not columnHeader.columnData.canSort) then
			return
		end

		--get the latest column header selected
		---@type df_headercolumnframe
		local previousColumnHeader = headerFrame.columnHeadersCreated[headerFrame.columnSelected]
		previousColumnHeader.Arrow:Hide()
		headerFrame:ResetColumnHeaderBackdrop(previousColumnHeader)
		headerFrame:SetBackdropColorForSelectedColumnHeader(columnHeader)

		if (headerFrame.columnSelected == columnHeader.columnIndex) then
			columnHeader.order = columnHeader.order ~= "ASC" and "ASC" or "DESC"
		end
		headerFrame.columnOrder = columnHeader.order

		--set the new column header selected
		headerFrame.columnSelected = columnHeader.columnIndex

		headerFrame:UpdateSortArrow(columnHeader)

		if (headerFrame.options.header_click_callback) then
			--callback with the main header frame, column header, column index and column order as payload
			local okay, errortext = pcall(headerFrame.options.header_click_callback, headerFrame, columnHeader, columnHeader.columnIndex, columnHeader.order)
			if (not okay) then
				print("DF: Header onClick callback error:", errortext)
			end
		end
	end,

	---comment
	---@param self button
	---@param buttonClicked string
	OnMouseDown = function(self, buttonClicked)
		if (buttonClicked == "LeftButton") then

		end
	end,

	---comment
	---@param self button
	---@param buttonClicked string
	OnMouseUp = function(self, buttonClicked)
		if (buttonClicked == "LeftButton") then

		end
	end,
}

---@class df_headermixin : table
detailsFramework.HeaderMixin = {
	---@param self df_headerframe
	---@param columnId number
	---@return number
	GetColumnWidth = function(self, columnId)
		return self.HeaderTable[columnId].width
	end,

	---@param self df_headerframe
	---@param newTable table
	SetHeaderTable = function(self, newTable)
		self.columnHeadersCreated = self.columnHeadersCreated or {}
		self.HeaderTable = newTable
		self.NextHeader = 1
		self.HeaderWidth = 0
		self.HeaderHeight = 0
		self:Refresh()
	end,

	---@param self df_headerframe
	---@param func function
	---@return boolean
	SetColumnSettingChangedCallback = function(self, func)
		if (type(func) ~= "function") then
			self.OnColumnSettingChangeCallback = nil
			return false
		end
		self.OnColumnSettingChangeCallback = func
		return true
	end,

	--return which header is current selected and the the order ASC DESC
	---@param self df_headerframe
	---@return number, string, string, string
	GetSelectedColumn = function(self)
		---@type number
		local columnSelected = self.columnSelected
		---@type df_headercolumnframe
		local columnHeader = self.columnHeadersCreated[columnSelected or 1]
		return columnSelected, columnHeader.order, columnHeader.key, columnHeader.columnData.name
	end,

	--clean up and rebuild the header following the header options
	--@self: main header frame
	---@param self df_headerframe
	Refresh = function(self)
		--refresh background frame
		self:SetBackdrop(self.options.backdrop)
		self:SetBackdropColor(unpack(self.options.backdrop_color))
		self:SetBackdropBorderColor(unpack(self.options.backdrop_border_color))

		--reset all header frames
		for i = 1, #self.columnHeadersCreated do
			local columnHeader = self.columnHeadersCreated[i]
			columnHeader.bInUse = false
			columnHeader:Hide()
		end

		local previousColumnHeader
		local growDirection = string.lower(self.options.grow_direction)

		--amount of headers to be updated
		local headerSize = #self.HeaderTable

		--update header frames
		for i = 1, headerSize do
			--get the header button, a new one is created if it doesn't exists yet
			local columnHeader = self:GetNextHeader()
			self:UpdateColumnHeader(columnHeader, i)

			--grow direction
			if (not previousColumnHeader) then
				columnHeader:SetPoint("topleft", self, "topleft", 0, 0)

				if (growDirection == "right") then
					if (self.options.use_line_separators) then
						columnHeader.Separator:Show()
						columnHeader.Separator:SetWidth(self.options.line_separator_width)
						columnHeader.Separator:SetColorTexture(unpack(self.options.line_separator_color))

						columnHeader.Separator:ClearAllPoints()
						if (self.options.line_separator_gap_align) then
							columnHeader.Separator:SetPoint("topleft", columnHeader, "topright", 0, 0)
						else
							columnHeader.Separator:SetPoint("topright", columnHeader, "topright", 0, 0)
						end
						columnHeader.Separator:SetHeight(self.options.line_separator_height)
					end
				end
			else
				if (growDirection == "right") then
					columnHeader:SetPoint("topleft", previousColumnHeader, "topright", self.options.padding, 0)

					if (self.options.use_line_separators) then
						columnHeader.Separator:Show()
						columnHeader.Separator:SetWidth(self.options.line_separator_width)
						columnHeader.Separator:SetColorTexture(unpack(self.options.line_separator_color))

						columnHeader.Separator:ClearAllPoints()
						if (self.options.line_separator_gap_align) then
							columnHeader.Separator:SetPoint("topleft", columnHeader, "topright", 0, 0)
						else
							columnHeader.Separator:SetPoint("topleft", columnHeader, "topright", 0, 0)
						end
						columnHeader.Separator:SetHeight(self.options.line_separator_height)

						if (headerSize == i) then
							columnHeader.Separator:Hide()
						end
					end

				elseif (growDirection == "left") then
					columnHeader:SetPoint("topright", previousColumnHeader, "topleft", -self.options.padding, 0)

				elseif (growDirection == "bottom") then
					columnHeader:SetPoint("topleft", previousColumnHeader, "bottomleft", 0, -self.options.padding)

				elseif (growDirection == "top") then
					columnHeader:SetPoint("bottomleft", previousColumnHeader, "topleft", 0, self.options.padding)
				end
			end

			previousColumnHeader = columnHeader
		end

		self:SetSize(self.HeaderWidth, self.HeaderHeight)
	end,

	---@param self df_headerframe
	---@param columnHeader df_headercolumnframe
	---@param defaultShown boolean
	---@param defaultOrder string
	UpdateSortArrow = function(self, columnHeader, defaultShown, defaultOrder)
		local options = self.options
		local order = defaultOrder or columnHeader.order
		local arrowIcon = columnHeader.Arrow

		if (type(defaultShown) ~= "boolean") then
			arrowIcon:Show()
		else
			arrowIcon:SetShown(defaultShown)
			if (defaultShown) then
				self:SetBackdropColorForSelectedColumnHeader(columnHeader)
			end
		end

		arrowIcon:SetAlpha(options.arrow_alpha)

		if (order == "ASC") then
			arrowIcon:SetTexture(options.arrow_up_texture)
			arrowIcon:SetTexCoord(unpack(options.arrow_up_texture_coords))
			arrowIcon:SetSize(unpack(options.arrow_up_size))

		elseif (order == "DESC") then
			arrowIcon:SetTexture(options.arrow_down_texture)
			arrowIcon:SetTexCoord(unpack(options.arrow_down_texture_coords))
			arrowIcon:SetSize(unpack(options.arrow_down_size))
		end
	end,

	---@param self df_headerframe
	---@param columnHeader df_headercolumnframe
	---@param headerIndex number
	UpdateColumnHeader = function(self, columnHeader, headerIndex)
		--this is the data to update the columnHeader
		local columnData = self.HeaderTable[headerIndex]
		columnHeader.key = columnData.key or "total"

		if (columnData.icon) then
			columnHeader.Icon:SetTexture(columnData.icon)

			if (columnData.texcoord) then
				columnHeader.Icon:SetTexCoord(unpack(columnData.texcoord))
			else
				columnHeader.Icon:SetTexCoord(0, 1, 0, 1)
			end

			columnHeader.Icon:SetPoint("left", columnHeader, "left", self.options.padding, 0)
			columnHeader.Icon:Show()
		end

		if (columnData.text) then
			columnHeader.Text:SetText(columnData.text)

			--text options
			detailsFramework:SetFontColor(columnHeader.Text, self.options.text_color)
			detailsFramework:SetFontSize(columnHeader.Text, self.options.text_size)
			detailsFramework:SetFontOutline(columnHeader.Text, self.options.text_shadow)

			--point
			if (not columnData.icon) then
				columnHeader.Text:SetPoint("left", columnHeader, "left", self.options.padding, 0)
			else
				columnHeader.Text:SetPoint("left", columnHeader.Icon, "right", self.options.padding, 0)
			end

			columnHeader.Text:Show()
		end

		--column header index
		columnHeader.columnIndex = headerIndex

		if (columnData.canSort) then
			columnHeader.order = "DESC"
			columnHeader.Arrow:SetTexture(self.options.arrow_up_texture)
		else
			columnHeader.Arrow:Hide()
		end

		if (columnData.selected) then
			columnHeader.Arrow:Show()
			columnHeader.Arrow:SetAlpha(.843)
			self:UpdateSortArrow(columnHeader, true, columnHeader.order)
			self.columnSelected = headerIndex
		else
			if (columnData.canSort) then
				self:UpdateSortArrow(columnHeader, false, columnHeader.order)
			end
		end

		--size
		if (columnData.width) then
			columnHeader:SetWidth(columnData.width)
		end
		if (columnData.height) then
			columnHeader:SetHeight(columnData.height)
		end

		columnHeader.XPosition = self.HeaderWidth -- + self.options.padding
		columnHeader.YPosition = self.HeaderHeight -- + self.options.padding
		
		columnHeader.columnAlign = columnData.align or "left"
		columnHeader.columnOffset = columnData.offset or 0

		--add the header piece size to the total header size
		local growDirection = string.lower(self.options.grow_direction)

		if (growDirection == "right" or growDirection == "left") then
			self.HeaderWidth = self.HeaderWidth + columnHeader:GetWidth() + self.options.padding
			self.HeaderHeight = math.max(self.HeaderHeight, columnHeader:GetHeight())

		elseif (growDirection == "top" or growDirection == "bottom") then
			self.HeaderWidth =  math.max(self.HeaderWidth, columnHeader:GetWidth())
			self.HeaderHeight = self.HeaderHeight + columnHeader:GetHeight() + self.options.padding
		end

		local bShowColumnHeaderReziser = self.options.reziser_shown
		if (bShowColumnHeaderReziser) then
			local resizerButton = columnHeader.resizerButton
			resizerButton:Show()
			resizerButton.texture:SetVertexColor(unpack(self.options.reziser_color))
			resizerButton:SetWidth(self.options.reziser_width)
			resizerButton:SetHeight(columnHeader:GetHeight())
		else
			columnHeader.resizerButton:Hide()
		end

		columnHeader:Show()
		columnHeader.bInUse = true
		columnHeader.columnData = columnData
	end,

	---reset column header backdrop
	---@param self df_headerframe
	---@param columnHeader df_headercolumnframe
	ResetColumnHeaderBackdrop = function(self, columnHeader)
		columnHeader:SetBackdrop(self.options.header_backdrop)
		columnHeader:SetBackdropColor(unpack(self.options.header_backdrop_color))
		columnHeader:SetBackdropBorderColor(unpack(self.options.header_backdrop_border_color))
	end,

	---@param self df_headerframe
	---@param columnHeader df_headercolumnframe
	SetBackdropColorForSelectedColumnHeader = function(self, columnHeader)
		columnHeader:SetBackdropColor(unpack(self.options.header_backdrop_color_selected))
	end,

	---clear the column header
	---@param self df_headerframe
	---@param columnHeader df_headercolumnframe
	ClearColumnHeader = function(self, columnHeader)
		columnHeader:SetSize(self.options.header_width, self.options.header_height)
		self:ResetColumnHeaderBackdrop(columnHeader)

		columnHeader:ClearAllPoints()

		columnHeader.Icon:SetTexture("")
		columnHeader.Icon:Hide()
		columnHeader.Text:SetText("")
		columnHeader.Text:Hide()
	end,

	---get the next column header, create one if doesn't exists
	---@param self df_headerframe
	GetNextHeader = function(self)
		local nextHeader = self.NextHeader
		local columnHeader = self.columnHeadersCreated[nextHeader]

		if (not columnHeader) then
			--create a new column header
			---@type df_headercolumnframe
			columnHeader = CreateFrame("button", "$parentHeaderIndex" .. nextHeader, self, "BackdropTemplate")
			columnHeader:SetScript("OnClick", detailsFramework.HeaderFunctions.OnClick)
			columnHeader:SetMovable(true)
			columnHeader:SetResizable(true)

			--header icon
			detailsFramework:CreateImage(columnHeader, "", self.options.header_height, self.options.header_height, "ARTWORK", nil, "Icon", "$parentIcon")
			--header separator
			detailsFramework:CreateImage(columnHeader, "", 1, 1, "ARTWORK", nil, "Separator", "$parentSeparator")
			--header name text
			detailsFramework:CreateLabel(columnHeader, "", self.options.text_size, self.options.text_color, "GameFontNormal", "Text", "$parentText", "ARTWORK")
			--header selected and order icon
			detailsFramework:CreateImage(columnHeader, self.options.arrow_up_texture, 12, 12, "ARTWORK", nil, "Arrow", "$parentArrow")

			---rezise button
			---@type df_headerresizer
			local resizerButton = CreateFrame("button", "$parentResizer", columnHeader)
			resizerButton:SetWidth(4)
			resizerButton:SetFrameLevel(columnHeader:GetFrameLevel()+2)
			resizerButton:SetPoint("topright", columnHeader, "topright", -1, -1)
			resizerButton:SetPoint("bottomright", columnHeader, "bottomright", -1, 1)
			resizerButton:EnableMouse(true)
			resizerButton:RegisterForClicks("LeftButtonDown", "LeftButtonUp")
			columnHeader.resizerButton = resizerButton

			resizerButton:SetScript("OnEnter", function()
				resizerButton.texture:SetVertexColor(1, 1, 1, 0.9)
			end)

			resizerButton:SetScript("OnLeave", function()
				resizerButton.texture:SetVertexColor(unpack(self.options.reziser_color))
			end)

			resizerButton:SetScript("OnMouseDown", function() --move this to a single function
				if (not columnHeader.bIsRezising) then
					--get the string length to know the min size
					local textLength = columnHeader.Text:GetStringWidth() + 6
					columnHeader:SetResizeBounds(math.max(textLength, self.options.reziser_min_width), columnHeader:GetHeight(), self.options.reziser_max_width, columnHeader:GetHeight())
					columnHeader.bIsRezising = true
					columnHeader:StartSizing("right")
				end
			end)

			resizerButton:SetScript("OnMouseUp", function()
				if (columnHeader.bIsRezising) then
					columnHeader.bIsRezising = false
					columnHeader:StopMovingOrSizing()

					--callback or modify into a passed by table?
					if (self.OnColumnSettingChangeCallback) then --need to get the header name
						local columnName = columnHeader.columnData.name
						xpcall(self.OnColumnSettingChangeCallback, geterrorhandler(), self, "width", columnName, columnHeader:GetWidth())
					end
				end
			end)

			resizerButton:SetScript("OnHide", function()
				if (columnHeader.bIsRezising) then
					columnHeader:StopMovingOrSizing()
					columnHeader.bIsRezising = false
				end
			end)

			resizerButton.texture = resizerButton:CreateTexture(nil, "overlay")
			resizerButton.texture:SetAllPoints()
			resizerButton.texture:SetColorTexture(1, 1, 1, 1)

			local xOffset = self.options.reziser_shown and -5 or -1
			columnHeader.Arrow:SetPoint("right", columnHeader, "right", xOffset, 0)

			columnHeader.Separator:Hide()
			columnHeader.Arrow:Hide()

			self:UpdateSortArrow(columnHeader, false, "DESC")

			table.insert(self.columnHeadersCreated, columnHeader)
			columnHeader = columnHeader
		end

		self:ClearColumnHeader(columnHeader)
		self.NextHeader = self.NextHeader + 1
		return columnHeader
	end,

	---return a header button by passing its name (.name on the column table)
	---@param self df_headerframe
	---@param columnName string
	---@return df_headercolumnframe|nil
	GetHeaderColumnByName = function(self, columnName)
		for _, headerColumnFrame in ipairs(self.columnHeadersCreated) do
			if (headerColumnFrame.columnData.name == columnName) then
				return headerColumnFrame
			end
		end
	end,

	NextHeader = 1,
	HeaderWidth = 0,
	HeaderHeight = 0,
}

--default options
local default_header_options = {
	backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	backdrop_color = {0, 0, 0, 0.2},
	backdrop_border_color = {0.1, 0.1, 0.1, .2},

	text_color = {1, 1, 1, 1},
	text_size = 10,
	text_shadow = false,
	grow_direction = "RIGHT",
	padding = 2,

	reziser_shown = false, --make sure to set the callback function with: header:SetOnColumnResizeScript(callbackFunction)
	reziser_width = 2,
	reziser_color = {1, 0.6, 0, 0.6},
	reziser_min_width = 16,
	reziser_max_width = 200,

	--each piece of the header
	header_backdrop = {bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	header_backdrop_color = {0, 0, 0, 0.5},
	header_backdrop_color_selected = {0.3, 0.3, 0.3, 0.5},
	header_backdrop_border_color = {0, 0, 0, 0},
	header_width = 120,
	header_height = 20,

	arrow_up_texture = [[Interface\Buttons\Arrow-Up-Down]],
	arrow_up_texture_coords = {0, 1, 6/16, 1},
	arrow_up_size = {12, 11},
	arrow_down_texture = [[Interface\Buttons\Arrow-Down-Down]],
	arrow_down_texture_coords = {0, 1, 0, 11/16},
	arrow_down_size = {12, 11},
	arrow_alpha = 0.659,

	use_line_separators = false,
	line_separator_color = {.1, .1, .1, .6},
	line_separator_width = 1,
	line_separator_height = 200,
	line_separator_gap_align = false,
}

---create a df_headerframe, alias 'header'.
---a header is a frame that can hold multiple columns which are also frames, each column is a df_headercolumnframe, these columns are arranged in horizontal form.
---a header is used to organize columns giving them a name/title, a way to sort and align them.
---each column is placed on the right side of the previous column.
---@param parent frame
---@param headerTable table
---@param options table|nil
---@param frameName string|nil
---@return df_headerframe
function detailsFramework:CreateHeader(parent, headerTable, options, frameName)
	---create the header frame which is returned by this function
	---@type df_headerframe
	local newHeader = CreateFrame("frame", frameName or "$parentHeaderLine", parent, "BackdropTemplate")

	detailsFramework:Mixin(newHeader, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(newHeader, detailsFramework.HeaderMixin)

	newHeader:BuildOptionsTable(default_header_options, options)

	--set the backdrop and backdrop color following the values in the options table
	newHeader:SetBackdrop(newHeader.options.backdrop)
	newHeader:SetBackdropColor(unpack(newHeader.options.backdrop_color))
	newHeader:SetBackdropBorderColor(unpack(newHeader.options.backdrop_border_color))

	newHeader:SetHeaderTable(headerTable)

	return newHeader
end


--[=[example:
C_Timer.After(1, function()


	local parent = UIParent

	--declare the columns the headerFrame will have
	---@type df_headercolumndata[]
	local headerTable = {
		{name = "playername", text = "Player Name", width = 120, align = "left", canSort = true},
		{name = "damage", text = "Damage Done", width = 80, align = "right", canSort = true},
		{name = "points", text = "Total Points", width = 80, align = "right", canSort = false},
	}
	local frameName = "MyAddOnOptionsFrame"
	local options = {}

	local headerFrame = DetailsFramework:CreateHeader(parent, headerTable, options, frameName)
	headerFrame:SetPoint("center", parent, "center", 10, -10)


end)
--]=]