---@class AddonPrivate
local Private = select(2, ...)

---@class UI
local ui = {
    ---@class Meta_Frame
    frame = nil,
    ---@class AceGUITreeGroup
    tree = nil,
    queuedUpdate = false
}
Private.UI = ui

local const = Private.constants
local treeGroup

local framePool = {}
local function isReleased(frame)
    for i, f in ipairs(framePool) do
        if f == frame then
            return true
        end
    end
    return false
end

local function getSubFrame(childData)
    local frame = tremove(framePool)
    if not frame then
        frame = CreateFrame("Frame", nil, UIParent)

        local icon = CreateFrame("Frame", nil, frame)
        icon.texture = icon:CreateTexture(nil, "ARTWORK")
        icon.texture:SetTexture(2065618)
        icon.texture:SetPoint("CENTER", 0, 3)
        icon.texture:SetSize(50, 50)
        icon.frame = icon:CreateTexture(nil, "OVERLAY")
        icon.frame:SetTexture("Interface\\AchievementFrame\\UI-Achievement-IconFrame")
        icon.frame:SetPoint("CENTER", -1, 2)
        icon.frame:SetTexCoord(0, 0.5625, 0, 0.5625)
        icon.frame:SetSize(72, 72)
        icon:SetPoint("TOPLEFT", 18, -12)
        icon:SetSize(60, 60)
        icon:SetScale(.31)

        local title = frame:CreateFontString()
        title:SetPoint("LEFT", icon, "RIGHT", 5, 0)
        title:SetPoint("RIGHT", frame, "RIGHT", -5, 0)
        title:SetJustifyH("LEFT")
        title:SetFontObject(const.FONT_OBJECTS.SMALL)
        title:SetHeight(20)
        title:SetText("test")

        local highlightTexture = frame:CreateTexture(nil, "HIGHLIGHT")
        highlightTexture:SetTexture("Interface\\Buttons\\WHITE8X8")
        highlightTexture:SetVertexColor(0.3, 0.3, 0, 0.5)
        highlightTexture:SetBlendMode("ADD")
        highlightTexture:SetAllPoints(frame)

        function frame:Release()
            frame:SetParent(nil)
            frame:ClearAllPoints()
            tinsert(framePool, frame)
            frame:Hide()
        end

        function frame:SetData(newChildData)
            frame.data = newChildData
            icon.texture:SetTexture(newChildData.icon or 878211)
            title:SetText(newChildData.text or "No Title")
        end

        frame:SetScript("OnEnter",function ()
            treeGroup:ShowTooltip(frame)
        end)
        frame:SetScript("OnLeave",function ()
            treeGroup:HideTooltip(frame)
        end)
        frame:SetScript("OnMouseDown", function (_, btnType)
            treeGroup:Expand(frame, btnType)
        end)

        frame:SetScript("OnHide", function ()
            if not isReleased(frame) then
                frame:Release()
            end
        end)
    end

    frame:Show()
    frame:SetData(childData)

    return frame
end

function ui:CreateUI()
    local aceGUI = Private.AceGUI
    ---@class Meta_Frame
    local mainUI = aceGUI:Create("Meta_Frame")
    mainUI:SetWidth(525)
    mainUI:SetHeight(620)
    mainUI:SetTitle(const.ADDON_NAME)
    mainUI:SetLayout("Fill")

    ---@class AceGUITreeGroup
    treeGroup = aceGUI:Create("Meta_ListGroup")
    treeGroup:SetFullWidth(true)
    treeGroup:SetFullHeight(true)
    treeGroup:SetFilterFunction(Private.WaypointsUtil.FilterFunction)
    mainUI:AddChild(treeGroup)
    treeGroup:SetCallback("OnTabSelected", function (_, _, selected)
        self:UpdateSelection(selected)
    end)
    treeGroup:SetCallback("OnExpandRightClick", function (_, _, data)
        if not data.waypointData or #data.waypointData <= 0 then return end
        Private.WaypointsUtil:AddCustomPins(unpack(data.waypointData))
        WorldMapFrame:Show()
    end)

    treeGroup:SetElementInitializer(function (frame, data)
        if not frame.btn then
			local button = CreateFrame("Button", nil, frame, "BackdropTemplate")
			button:SetBackdrop({
				bgFile = "Interface\\AchievementFrame\\UI-Achievement-Parchment-Horizontal",
				edgeFile = "Interface\\glues\\common\\glue-tooltip-border",
				tile = false,
				tileSize = 0,
				edgeSize = 16,
				insets = { left = 8, right = 8, top = 8, bottom = 8 },
			})
			button:SetBackdropBorderColor(1, 1, 1, 1)
            button:RegisterForClicks("AnyDown")

			button:SetScript("OnClick", function (_, btnType)
                treeGroup:Expand(frame, btnType)
                if Private.RewardPreview and frame.data then
                    local achID = tonumber(tostring(frame.data.value):match("^(%d+)"))
                    if achID then Private.RewardPreview:ShowForAchievement(achID) end
                end
            end)
			button:SetScript("OnEnter",function ()
                treeGroup:ShowTooltip(frame)
                if frame.data.children then
                    button:SetBackdropBorderColor(1, 0.85, 0, 1)
                end
            end)
			button:SetScript("OnLeave",function ()
                treeGroup:HideTooltip(frame)
                if frame.data.children then
			        button:SetBackdropBorderColor(1, 1, 1, 1)
                end
            end)

			button:SetPoint("TOPLEFT")
			button:SetPoint("BOTTOMRIGHT", 0, 5)

            local titleBar = button:CreateTexture()
            titleBar:SetTexture("Interface\\AchievementFrame\\UI-Achievement-Borders")
            titleBar:SetPoint("TOPLEFT", 12, -6)
            titleBar:SetPoint("TOPRIGHT", -5, -6)
            titleBar:SetHeight(24)
            titleBar:SetVertexColor(1, 1, 1, .8)
            titleBar:SetTexCoord(0, 0.9765625, 0.66015625, 0.73828125)

			local text = button:CreateFontString()
            text:SetPoint("TOP", titleBar, "TOP", 0, 0)
			text:SetFontObject(const.FONT_OBJECTS.NORMAL)
			text:SetHeight(20)
			button.text = text

            local rewardBackground = button:CreateTexture()
            rewardBackground:SetTexture("Interface\\AchievementFrame\\UI-Achievement-Reward-Background")
            rewardBackground:SetPoint("BOTTOMLEFT", 5, 5)
            rewardBackground:SetPoint("BOTTOMRIGHT", -5, 5)
            rewardBackground:SetHeight(36)
            rewardBackground:SetTexCoord(0, .69, 0, .75)
            button.rewardBackground = rewardBackground

            local rewardText = button:CreateFontString()
            rewardText:SetFontObject(const.FONT_OBJECTS.SMALL)
            -- Bound the reward text horizontally so it word-wraps instead of clipping off the box.
            rewardText:SetPoint("TOPLEFT", rewardBackground, "TOPLEFT", 6, -1)
            rewardText:SetPoint("BOTTOMRIGHT", rewardBackground, "BOTTOMRIGHT", -6, 1)
            rewardText:SetJustifyH("CENTER")
            rewardText:SetJustifyV("MIDDLE")
            rewardText:SetWordWrap(true)
            rewardText:SetMaxLines(2)
            button.rewardText = rewardText

            local icon = CreateFrame("Frame", nil, button)
            icon.texture = icon:CreateTexture(nil, "ARTWORK")
            icon.texture:SetTexture(2065618)
            icon.texture:SetPoint("CENTER", 0, 3)
            icon.texture:SetSize(50, 50)
            icon.frame = icon:CreateTexture(nil, "OVERLAY")
            icon.frame:SetTexture("Interface\\AchievementFrame\\UI-Achievement-IconFrame")
            icon.frame:SetPoint("CENTER", -1, 2)
            icon.frame:SetTexCoord(0, 0.5625, 0, 0.5625)
            icon.frame:SetSize(72, 72)
            icon:SetPoint("TOPLEFT", 18, -12)
            icon:SetSize(60, 60)
            icon:SetScale(.8)

            local childContainer = CreateFrame("Frame", nil, frame)
            childContainer:SetPoint("TOPLEFT", 75, -40)
            childContainer:SetPoint("BOTTOMRIGHT", -50, 50)
            frame.childContainer = childContainer

            function frame:Expand(isExpanded)
                local maxChildWidth = frame.childContainer:GetWidth()
			    frame:SetHeight(isExpanded and self.data.expandHeight or self.data.height)
                if isExpanded then
                    for childIndex, child in ipairs(frame.data.children) do
                        local f = getSubFrame(child)
                        f:SetParent(frame.childContainer)

                        local row = math.ceil(childIndex / 2)
                        local col = ((childIndex - 1) % 2) + 1

                        local xOffset = ((col - 1) * (maxChildWidth / 2))
                        local yOffset = -((row - 1) * 30)

                        local width = (maxChildWidth / 2) - 5

                        f:SetPoint("TOPLEFT", xOffset, yOffset)
                        f:SetSize(width, 25)
                    end
                else
                    for _, childFrame in ipairs({frame.childContainer:GetChildren()}) do
                        childFrame:Release()
                    end
                end
            end

            frame:SetScript("OnHide", function ()
                for _, childFrame in ipairs({frame.childContainer:GetChildren()}) do
                    childFrame:Release()
                end
            end)

            button.icon = icon

			frame.btn = button
		end
        frame.data = data
		local button = frame.btn
        if data.children then
			button:SetBackdropBorderColor(1, 1, 1, 1)
        else
			button:SetBackdropBorderColor(1, 1, 1, 0)
        end
        button.text:SetText(data.icon and string.format("|T%s:16|t %s", data.icon, data.text) or data.text)

        local id = tonumber(tostring(data.value):match("^(%d+)"))
        local achievementInfo = {GetAchievementInfo(id)}
        local achIcon = achievementInfo[10] or "Interface\\Icons\\INV_Misc_QuestionMark"
        local achReward = achievementInfo[11] or ""
        button.icon.texture:SetTexture(achIcon)
        button.rewardText:SetText(achReward)
        button.rewardBackground:SetShown(achReward ~= "")
        if data.children then
            data.expandHeight = data.height + (30 * (math.ceil(#data.children / 2)))
        end
		button:SetSize(200, data.expanded and data.expandHeight or data.height)

        if data.expanded then
            frame:Expand(true)
        else
            frame:Expand(false)
        end
    end)
    treeGroup:SetElementExtentCalculator(function (_, data)
        if data.expanded and data.children then
			return data.expandHeight
		end
		return data.height
    end)
    treeGroup:EnableButtonTooltips(true)
    mainUI:UpdateHelpText(self:CreateMissingText())

    mainUI:Hide()
    self.frame = mainUI
    self.tree = treeGroup

    -- Any close path (X button, Escape, SetShown) hides the reward model panel too.
    if mainUI.frame then
        mainUI.frame:HookScript("OnHide", function()
            if Private.RewardPreview and Private.RewardPreview.frame then
                Private.RewardPreview.frame:Hide()
            end
        end)

        -- Prominent toggle for the Do-it-now Planner so players discover the feature.
        local doItNow = CreateFrame("Button", nil, mainUI.frame, "UIPanelButtonTemplate")
        doItNow:SetSize(104, 22)
        doItNow:SetPoint("TOPRIGHT", mainUI.frame, "TOPRIGHT", -30, -7)
        doItNow:SetText("Do-it-now")
        doItNow:SetFrameLevel(mainUI.frame:GetFrameLevel() + 5)
        doItNow:SetScript("OnClick", function()
            if Private.RouteHud then Private.RouteHud:Toggle() end
        end)
        doItNow:SetScript("OnEnter", function(b)
            GameTooltip:SetOwner(b, "ANCHOR_BOTTOMLEFT")
            GameTooltip:AddLine("Do-it-now Planner", 1, 0.82, 0)
            GameTooltip:AddLine("A smart, cross-zone to-do route: nearest objectives, other "
                .. "zones, dungeons/raids with one-click group finder, and world quests.", 1, 1, 1, true)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("Click to open or close the panel.", 0, 1, 0)
            GameTooltip:Show()
        end)
        doItNow:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end

    self:UpdateTabs()
    self:UpdateSelection(1)
end

function ui:UpdateTabs()
    if not self.tree or not self.frame then return end
    self.tree:SetTabs(Private.ExpansionUtil:GetExpansions(), self.frame.frame)
end

local lastSelection = 1
function ui:UpdateSelection(selection)
    selection = selection or lastSelection
    lastSelection = selection
    local tree = Private.WaypointsUtil:BuildTree(Private.ExpansionUtil:GetExpansion(selection))
    self.tree:SetProgress(Private.WaypointsUtil:GetProgress(Private.ExpansionUtil:GetExpansion(selection)))
    self.tree:SetData(tree)
    -- the previewed reward belonged to the previous view; hide until a row is clicked
    if Private.RewardPreview and Private.RewardPreview.frame then
        Private.RewardPreview.frame:Hide()
    end
end

function ui:ToggleUI()
    if not self.frame then return end
    self:SetShown(not self.frame:IsVisible())

    if self.frame:IsVisible() then
        if self.queuedUpdate then
            self:UpdateSelection()
            self.queuedUpdate = nil
        end
    end
end

function ui:QueueUpdate()
    self.queuedUpdate = true
end

-- Open the main window, select the given expansion tab, and scroll/expand to the
-- achievement (used by the Do-it-now Dungeons & Raids shift-click).
function ui:ShowAchievement(expansionIndex, achievementID)
    if not self.frame then return end
    self:SetShown(true)
    if expansionIndex then
        self:UpdateSelection(expansionIndex)
        if self.tree and self.tree.frame and PanelTemplates_SetTab then
            pcall(PanelTemplates_SetTab, self.tree.frame, expansionIndex)
        end
    end
    C_Timer.After(0, function()
        local sv = self.tree and self.tree.scrollview
        local dp = sv and sv:GetDataProvider()
        if not dp then return end
        local index, data = dp:FindByPredicate(function(searchData)
            return searchData.value == achievementID
        end)
        if index then
            self.tree:JumpToIndex(index, data)
        end
    end)
end

function ui:SetShown(shown)
    if not self.frame then return end
    if shown then
        self.frame:Show()
    else
        self.frame:Hide()
        if Private.RewardPreview and Private.RewardPreview.frame then
            Private.RewardPreview.frame:Hide()
        end
    end
end

function ui:CreateMissingText()
    return "Did you know that you can now download expansions ?\nYou're missing out on the following expansions:\n" .. table.concat(Private.ExpansionUtil:GetUnloadedExpansions(), "\n")
end