GuildHelper = GuildHelper or {}

local addon = GuildHelper

addon.NewUINews = addon.NewUINews or {}

local page = addon.NewUINews
local PANEL_ART_KEY = "MyToonsPanelBackground"
local PANEL_HEADER_KEY = "HomePanelHeader"
local CARD_ART_KEY = "MyToonsCardBackground"
local BUTTON_ART_KEY = "MyToonsButtonBackground"
local BUTTON_SELECTED_ART_KEY = "MyToonsButtonSelected"
local INPUT_ART_KEY = "MyToonsRowBackground"
local NEWS_ICON_PATH = "Interface\\AddOns\\GH3\\Media\\Icons\\Icon_News-l.png"
local EVENT_ICON_PATH = "Interface\\AddOns\\GH3\\Media\\Icons\\Icon_Calendar-l.png"
local RESOURCE_ICON_PATH = "Interface\\AddOns\\GH3\\Media\\Icons\\Icon_BIS-l.png"
local LEGACY_CONTENT_OFFSET_Y = 28
local refreshPage

local function ApplyRect(frame, parent, rect)
    frame:ClearAllPoints()
    frame:SetPoint("TOPLEFT", parent, "TOPLEFT", tonumber(rect.x) or 0, tonumber(rect.y) or 0)
    frame:SetSize(tonumber(rect.w) or 1, tonumber(rect.h) or 1)
end

local function cleanupFrameTree(frame)
    if not frame then
        return
    end

    for _, child in ipairs({ frame:GetChildren() }) do
        cleanupFrameTree(child)
    end

    if type(frame.UnregisterAllEvents) == "function" then
        frame:UnregisterAllEvents()
    end

    if type(frame.SetScript) == "function" then
        frame:SetScript("OnEvent", nil)
        frame:SetScript("OnUpdate", nil)
    end

    if frame.Hide then
        frame:Hide()
    end

    if frame.SetParent then
        frame:SetParent(nil)
    end
end

local function isNewsPageActive()
    return addon.NewUIShell and addon.NewUIShell.activePage == "news"
end

local function resolveTexturePath(textureKey)
    local textures = addon.NewUIAssets and addon.NewUIAssets.Textures or nil
    return textures and textures[textureKey] or nil
end

local function resolveArtMetrics(textureKey)
    local metrics = addon.NewUIAssets and addon.NewUIAssets.Metrics or nil
    return metrics and metrics[textureKey] or nil
end

local function applyArtTexture(texture, textureKey, fallbackColor)
    local texturePath = resolveTexturePath(textureKey)
    local metrics = resolveArtMetrics(textureKey)

    if texturePath then
        texture:SetTexture(texturePath)
        texture:SetVertexColor(1, 1, 1, 0.98)
        if metrics and type(metrics.texCoord) == "table" and #metrics.texCoord == 4 then
            texture:SetTexCoord(metrics.texCoord[1], metrics.texCoord[3], metrics.texCoord[2], metrics.texCoord[4])
        end
        return
    end

    texture:SetColorTexture(unpack(fallbackColor or { 0.10, 0.10, 0.12, 0.94 }))
end

local function applyBadgeTexture(texture, texturePath, fallbackColor)
    if texturePath and texturePath ~= "" then
        texture:SetTexture(texturePath)
        texture:SetTexCoord(0, 1, 0, 1)
        texture:SetVertexColor(1, 1, 1, 1)
        return
    end

    texture:SetColorTexture(unpack(fallbackColor or { 0.84, 0.72, 0.44, 1 }))
end

local function createArtContentFrame(parent, textureKey, width, height, fallbackInsets)
    local metrics = resolveArtMetrics(textureKey)
    local insets = metrics and metrics.insets or fallbackInsets or {}
    local sourceWidth = metrics and tonumber(metrics.sourceWidth) or tonumber(width) or 0
    local sourceHeight = metrics and tonumber(metrics.sourceHeight) or tonumber(height) or 0
    local scaleX = sourceWidth > 0 and (tonumber(width) or 0) / sourceWidth or 1
    local scaleY = sourceHeight > 0 and (tonumber(height) or 0) / sourceHeight or 1
    local left = math.floor(((tonumber(insets.left) or 0) * scaleX) + 0.5)
    local right = math.floor(((tonumber(insets.right) or 0) * scaleX) + 0.5)
    local top = math.floor(((tonumber(insets.top) or 0) * scaleY) + 0.5)
    local bottom = math.floor(((tonumber(insets.bottom) or 0) * scaleY) + 0.5)
    local content = CreateFrame("Frame", nil, parent)
    content:SetPoint("TOPLEFT", parent, "TOPLEFT", left, -top)
    content:SetSize(math.max(1, (tonumber(width) or 1) - left - right), math.max(1, (tonumber(height) or 1) - top - bottom))
    return content
end

local function createPanel(parent, rect, titleText)
    local panel = CreateFrame("Frame", nil, parent, BackdropTemplateMixin and "BackdropTemplate" or nil)
    ApplyRect(panel, parent, rect)
    local background = panel:CreateTexture(nil, "BACKGROUND")
    background:SetAllPoints(panel)
    applyArtTexture(background, PANEL_ART_KEY, { 0.09, 0.10, 0.13, 0.94 })

    local innerContent = createArtContentFrame(panel, PANEL_ART_KEY, rect.w, rect.h, { left = 24, right = 24, top = 42, bottom = 22 })
    local headerBand = panel:CreateTexture(nil, "ARTWORK")
    headerBand:SetPoint("TOPLEFT", panel, "TOPLEFT", 24, -14)
    headerBand:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -24, -14)
    headerBand:SetHeight(24)
    applyArtTexture(headerBand, PANEL_HEADER_KEY, { 0.08, 0.08, 0.10, 0.82 })

    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("CENTER", headerBand, "CENTER", 0, -1)
    title:SetText(titleText or "")
    title:SetTextColor(0.98, 0.92, 0.76, 1)

    panel.Content = innerContent
    return panel
end

local function createActionButton(parent, point, relativeTo, relativePoint, x, y, width, text, onClick)
    local button = CreateFrame("Button", nil, parent, BackdropTemplateMixin and "BackdropTemplate" or nil)
    button:SetSize(width, 30)
    button:SetPoint(point, relativeTo, relativePoint, x, y)
    button:SetScript("OnClick", onClick)

    local art = button:CreateTexture(nil, "ARTWORK", nil, 1)
    art:SetAllPoints(button)
    applyArtTexture(art, BUTTON_ART_KEY, { 0.10, 0.09, 0.07, 0.96 })
    button._art = art

    local label = button:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    label:SetPoint("CENTER", button, "CENTER", 0, 0)
    label:SetText(text)
    label:SetTextColor(0.96, 0.93, 0.84, 1)
    button.Label = label

    local function setArt(textureKey)
        applyArtTexture(art, textureKey, { 0.10, 0.09, 0.07, 0.96 })
    end

    local function updateEnabledVisual()
        if button:IsEnabled() then
            setArt(BUTTON_ART_KEY)
            label:SetTextColor(0.96, 0.93, 0.84, 1)
        else
            setArt(INPUT_ART_KEY)
            label:SetTextColor(0.62, 0.61, 0.58, 1)
        end
    end

    button:SetScript("OnEnter", function(self)
        if self:IsEnabled() then
            setArt(BUTTON_SELECTED_ART_KEY)
        end
    end)
    button:SetScript("OnLeave", function() updateEnabledVisual() end)
    button:SetScript("OnMouseDown", function(self)
        if self:IsEnabled() then
            setArt(BUTTON_SELECTED_ART_KEY)
        end
    end)
    button:SetScript("OnMouseUp", function(self)
        if self:IsEnabled() then
            setArt(self:IsMouseOver() and BUTTON_SELECTED_ART_KEY or BUTTON_ART_KEY)
        end
    end)
    button:SetScript("OnEnable", updateEnabledVisual)
    button:SetScript("OnDisable", updateEnabledVisual)
    updateEnabledVisual()
    return button
end

local function createInputHost(parent, point, relativeTo, relativePoint, x, y, width, height)
    local host = CreateFrame("Frame", nil, parent, BackdropTemplateMixin and "BackdropTemplate" or nil)
    host:SetPoint(point, relativeTo, relativePoint, x, y)
    host:SetSize(width, height)
    local art = host:CreateTexture(nil, "BACKGROUND")
    art:SetAllPoints(host)
    applyArtTexture(art, INPUT_ART_KEY, { 0.08, 0.08, 0.10, 0.96 })
    host._art = art
    return host
end

local function cycleOption(options, current)
    for index, value in ipairs(options) do
        if value == current then
            return options[(index % #options) + 1]
        end
    end
    return options[1]
end

local function createSectionFrame(parent, x, y, width, height, titleText, useHeaderArt)
    local frame = CreateFrame("Frame", nil, parent, BackdropTemplateMixin and "BackdropTemplate" or nil)
    frame:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    frame:SetSize(width, height)

    local background = frame:CreateTexture(nil, "BACKGROUND")
    background:SetAllPoints(frame)
    applyArtTexture(background, PANEL_ART_KEY, { 0.09, 0.10, 0.13, 0.94 })

    local hasHeaderArt = useHeaderArt ~= false
    local content = createArtContentFrame(frame, PANEL_ART_KEY, width, height, hasHeaderArt and { left = 24, right = 24, top = 42, bottom = 22 } or { left = 24, right = 24, top = 18, bottom = 22 })

    if hasHeaderArt then
        local header = frame:CreateTexture(nil, "ARTWORK")
        header:SetPoint("TOPLEFT", frame, "TOPLEFT", 24, -14)
        header:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -24, -14)
        header:SetHeight(24)
        applyArtTexture(header, PANEL_HEADER_KEY, { 0.08, 0.08, 0.10, 0.82 })

        local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        title:SetPoint("CENTER", header, "CENTER", 0, -1)
        title:SetText(titleText or "")
        title:SetTextColor(0.98, 0.92, 0.76, 1)
    end

    frame.Content = content
    return frame
end

local function initializeInput(editBox)
    if not editBox then
        return
    end

    editBox:SetAutoFocus(false)
    editBox:SetTextInsets(0, 0, 0, 0)
    editBox:SetTextColor(0.96, 0.93, 0.84, 1)

    if editBox.Left then editBox.Left:SetAlpha(0) end
    if editBox.Middle then editBox.Middle:SetAlpha(0) end
    if editBox.Right then editBox.Right:SetAlpha(0) end
end

local function formatBoardType(value)
    local text = tostring(value or "announcement")
    return string.upper(string.sub(text, 1, 1)) .. string.sub(text, 2)
end

local function formatBoardDate(timestamp)
    local value = tonumber(timestamp) or time()
    return date("%b %d, %Y", value)
end

local function hideLegacyTitle(frame, targetText)
    if not frame then
        return false
    end

    for _, region in ipairs({ frame:GetRegions() }) do
        if region and region.GetText and string.lower(tostring(region:GetText() or "")) == string.lower(targetText or "") then
            region:Hide()
            return true
        end
    end

    for _, child in ipairs({ frame:GetChildren() }) do
        if hideLegacyTitle(child, targetText) then
            return true
        end
    end

    return false
end

local function clearLegacySurface(frame)
    if not frame then
        return
    end

    if frame.SetBackdropColor then
        frame:SetBackdropColor(0, 0, 0, 0)
    end
    if frame.SetBackdropBorderColor then
        frame:SetBackdropBorderColor(0, 0, 0, 0)
    end

    if type(frame._gh3Depth) == "table" then
        for _, region in pairs(frame._gh3Depth) do
            if region and region.Hide then
                region:Hide()
            end
        end
    end
end

local function ensureSurfaceTexture(frame, textureKey)
    if not frame then
        return nil
    end

    local texture = frame._gh3NewsArtTexture
    if not texture then
        texture = frame:CreateTexture(nil, "BACKGROUND")
        texture:SetAllPoints(frame)
        frame._gh3NewsArtTexture = texture
    end

    applyArtTexture(texture, textureKey, { 0.09, 0.10, 0.13, 0.94 })
    return texture
end

local function skinLegacyButton(button)
    clearLegacySurface(button)
    ensureSurfaceTexture(button, BUTTON_ART_KEY)

    for _, texture in ipairs({ button.GetNormalTexture and button:GetNormalTexture() or nil, button.GetPushedTexture and button:GetPushedTexture() or nil, button.GetHighlightTexture and button:GetHighlightTexture() or nil, button.GetDisabledTexture and button:GetDisabledTexture() or nil }) do
        if texture then
            texture:SetAlpha(0)
        end
    end

    if button.GetFontString and button:GetFontString() then
        button:GetFontString():SetTextColor(button:IsEnabled() and 0.96 or 0.62, button:IsEnabled() and 0.93 or 0.62, button:IsEnabled() and 0.84 or 0.58, 1)
    end
end

local function skinLegacyInput(input)
    if not input or not input.GetParent then
        return
    end

    if input.Left then input.Left:SetAlpha(0) end
    if input.Middle then input.Middle:SetAlpha(0) end
    if input.Right then input.Right:SetAlpha(0) end

    local chrome = input._gh3NewsChrome
    if not chrome then
        chrome = CreateFrame("Frame", nil, input:GetParent(), BackdropTemplateMixin and "BackdropTemplate" or nil)
        chrome:SetFrameLevel(math.max((input:GetFrameLevel() or 1) - 1, 0))
        chrome:SetPoint("TOPLEFT", input, "TOPLEFT", -4, 4)
        chrome:SetPoint("BOTTOMRIGHT", input, "BOTTOMRIGHT", 4, -4)
        input._gh3NewsChrome = chrome
    end

    clearLegacySurface(chrome)
    ensureSurfaceTexture(chrome, INPUT_ART_KEY)
    input:SetTextColor(0.96, 0.93, 0.84, 1)
end

local function skinLegacyFontString(region)
    if not region or region:GetObjectType() ~= "FontString" then
        return
    end

    local text = tostring(region:GetText() or "")
    if text == "" then
        return
    end

    local parent = region.GetParent and region:GetParent() or nil
    local parentType = parent and parent.GetObjectType and parent:GetObjectType() or ""
    local fontPath, fontSize = region:GetFont()
    local isMuted = text == "Press Enter to search" or string.find(text, "^By ") ~= nil
    local isHeading = (tonumber(fontSize) or 12) >= 15 or string.sub(text, -1) == ":"

    if fontPath and region.SetFont then
        region:SetFont(fontPath, fontSize or (isHeading and 14 or 12), "")
    end

    if parentType == "Button" then
        region:SetTextColor(0.96, 0.93, 0.84, 1)
    elseif isMuted then
        region:SetTextColor(0.74, 0.72, 0.68, 1)
    elseif isHeading then
        region:SetTextColor(0.98, 0.90, 0.72, 1)
    else
        region:SetTextColor(0.90, 0.88, 0.82, 1)
    end

    region:SetShadowColor(0, 0, 0, 0.85)
    region:SetShadowOffset(1, -1)
end

local function skinLegacyFrame(frame)
    if not frame or frame:GetObjectType() ~= "Frame" or not frame.SetBackdrop then
        return
    end

    clearLegacySurface(frame)
    ensureSurfaceTexture(frame, (frame:GetHeight() or 0) >= 140 and PANEL_ART_KEY or CARD_ART_KEY)
end

local function skinLegacyTree(frame)
    if not frame then
        return
    end

    for _, region in ipairs({ frame:GetRegions() }) do
        skinLegacyFontString(region)
    end

    for _, child in ipairs({ frame:GetChildren() }) do
        local objectType = child.GetObjectType and child:GetObjectType() or ""

        if objectType == "Button" then
            skinLegacyButton(child)
        elseif objectType == "EditBox" then
            skinLegacyInput(child)
        elseif objectType == "Frame" then
            skinLegacyFrame(child)
        end

        skinLegacyTree(child)
    end
end

local function installMainFrameBridge()
    if page._bridgeInstalled or not addon.MainFrame then
        return
    end

    page._bridgeInstalled = true
    page._originalRefreshCurrentView = addon.MainFrame.RefreshCurrentView
    page._originalMarkBackgroundRefreshPending = addon.MainFrame.MarkBackgroundRefreshPending

    addon.MainFrame.RefreshCurrentView = function(self, ...)
        if isNewsPageActive() and page._parentFrame then
            refreshPage(page._parentFrame, page._snapshot)
            return
        end
        if page._originalRefreshCurrentView then
            return page._originalRefreshCurrentView(self, ...)
        end
    end

    addon.MainFrame.MarkBackgroundRefreshPending = function(self, ...)
        if isNewsPageActive() and page._parentFrame then
            refreshPage(page._parentFrame, page._snapshot)
            return
        end
        if page._originalMarkBackgroundRefreshPending then
            return page._originalMarkBackgroundRefreshPending(self, ...)
        end
    end
end

local function renderLegacyPane(host)
    local mainFrame = addon.MainFrame
    if not mainFrame or type(mainFrame.RenderBoard) ~= "function" then
        return
    end

    if mainFrame.EnsureState then
        mainFrame:EnsureState()
    end

    local originalViewHost = mainFrame.viewHost
    mainFrame.viewHost = host
    local ok, errorMessage = pcall(mainFrame.RenderBoard, mainFrame)
    mainFrame.viewHost = originalViewHost

    if not ok then
        local errorText = host:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        errorText:SetPoint("TOPLEFT", host, "TOPLEFT", 18, -18)
        errorText:SetText("News page failed to render: " .. tostring(errorMessage))
        errorText:SetTextColor(1, 0.3, 0.3, 1)
    end
end

refreshPage = function(parent, snapshot)
    if page._rootFrame then
        cleanupFrameTree(page._rootFrame)
        page._rootFrame = nil
    end
    page:Render(parent, snapshot)
end

function page:Render(parent, snapshot)
    local newsLayout = addon.NewUILayout and addon.NewUILayout.News or {}
    local root = CreateFrame("Frame", nil, parent)

    ApplyRect(root, parent, newsLayout.Page or { x = 0, y = 0, w = tonumber(parent:GetWidth()) or 1380, h = tonumber(parent:GetHeight()) or 742 })
    page._rootFrame = root
    page._parentFrame = parent
    page._snapshot = snapshot
    installMainFrameBridge()

    local mainFrame = addon.MainFrame
    if not mainFrame then
        return root
    end

    if mainFrame.EnsureState then
        mainFrame:EnsureState()
    end

    local guildOptions = addon.FederationService:GetAllGuildKeys()
    local boardTypeOptions = { "all", "announcement", "event", "resource" }
    local editableTypeOptions = { "announcement", "event", "resource" }
    local records = addon.ContentService:ListVisibleContent({
        searchText = mainFrame.state.boardFilterText,
        typeFilter = mainFrame.state.boardFilterType,
        guildName = mainFrame.state.boardFilterGuild,
        pinnedOnly = mainFrame.state.boardFilterPinnedOnly,
    })
    local selectedContentId = mainFrame:GetSelectedContentId()
    local selectedRecord = selectedContentId and addon.ContentService:GetContent(selectedContentId) or nil
    local canManageBoard = addon.ContentService:CanManageBoard()
    local selectedVisible = false

    for _, record in ipairs(records) do
        if record.id == selectedContentId then
            selectedVisible = true
            break
        end
    end

    if not selectedVisible then
        mainFrame:SetSelectedContent(records[1] and records[1].id or nil)
        selectedContentId = mainFrame:GetSelectedContentId()
        selectedRecord = selectedContentId and addon.ContentService:GetContent(selectedContentId) or nil
    end

    local basePanelRect = newsLayout.Panel or { x = 20, y = -36, w = 1340, h = 638 }
    local panel = createPanel(root, basePanelRect, "")
    local content = panel.Content
    local contentWidth = content:GetWidth()
    local contentHeight = content:GetHeight()
    local leftWidth = 438
    local gap = 18
    local paneTop = -74
    local paneHeight = contentHeight - 84
    local rightWidth = math.max(320, contentWidth - leftWidth - gap)

    local pageIcon = content:CreateTexture(nil, "ARTWORK")
    pageIcon:SetPoint("TOPLEFT", content, "TOPLEFT", 8, -2)
    pageIcon:SetSize(54, 54)
    applyBadgeTexture(pageIcon, NEWS_ICON_PATH, { 0.84, 0.72, 0.44, 1 })

    local pageTitle = content:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
    pageTitle:SetPoint("TOPLEFT", pageIcon, "TOPRIGHT", 14, -2)
    pageTitle:SetText("News")
    pageTitle:SetTextColor(0.98, 0.90, 0.64, 1)

    local titleDivider = content:CreateTexture(nil, "ARTWORK")
    titleDivider:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -64)
    titleDivider:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, -64)
    titleDivider:SetHeight(8)
    applyArtTexture(titleDivider, PANEL_HEADER_KEY, { 0.34, 0.25, 0.10, 0.82 })

    local leftPanel = createSectionFrame(content, 0, paneTop, leftWidth, paneHeight, "Board Posts", false)
    local rightPanel = createSectionFrame(content, leftWidth + gap, paneTop, rightWidth, paneHeight, "")

    local leftTitle = leftPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    leftTitle:SetPoint("TOPLEFT", leftPanel.Content, "TOPLEFT", 0, 14)
    leftTitle:SetText("Board Posts")
    leftTitle:SetTextColor(0.98, 0.92, 0.76, 1)

    local function refreshBoard()
        mainFrame:RefreshCurrentView(true)
    end

    local function getGuildFilterLabel()
        local value = tostring(mainFrame.state.boardFilterGuild or "all")
        if #value > 12 then
            value = string.sub(value, 1, 10) .. ".."
        end
        return "Guild: " .. value
    end

    local leftContentWidth = leftPanel.Content:GetWidth()
    local searchHost = createInputHost(leftPanel.Content, "TOPLEFT", leftPanel.Content, "TOPLEFT", 0, -18, leftContentWidth, 34)
    local searchInput = CreateFrame("EditBox", nil, searchHost, "InputBoxTemplate")
    initializeInput(searchInput)
    searchInput:SetPoint("TOPLEFT", searchHost, "TOPLEFT", 12, -7)
    searchInput:SetPoint("BOTTOMRIGHT", searchHost, "BOTTOMRIGHT", -12, 7)
    searchInput:SetText(mainFrame.state.boardFilterText or "")
    searchInput:SetScript("OnEnterPressed", function(self)
        mainFrame.state.boardFilterText = self:GetText()
        self:ClearFocus()
        refreshBoard()
    end)
    searchInput:SetScript("OnEscapePressed", function(self)
        self:ClearFocus()
    end)

    local newPostButton = createActionButton(leftPanel.Content, "TOPRIGHT", leftPanel.Content, "TOPRIGHT", 0, 12, 146, "+ New Post", function()
        local record = addon.ContentService:Create("announcement", "New Post", "", addon.Store:GetDisplayGuildKey() or "Unguilded")
        if not record then
            return
        end
        mainFrame:SetSelectedContent(record.id)
        mainFrame:SetBoardEditing(true)
        refreshBoard()
    end)

    if not canManageBoard then
        newPostButton:Disable()
    end

    local filterButtonWidth = math.floor((leftContentWidth - 10) / 2)
    local typeButton = createActionButton(leftPanel.Content, "TOPLEFT", searchHost, "BOTTOMLEFT", 0, -12, filterButtonWidth, "Type: " .. formatBoardType(mainFrame.state.boardFilterType or "all"), function()
        mainFrame.state.boardFilterType = cycleOption(boardTypeOptions, mainFrame.state.boardFilterType or "all")
        refreshBoard()
    end)
    local guildButton = createActionButton(leftPanel.Content, "TOPLEFT", typeButton, "TOPRIGHT", 10, 0, filterButtonWidth, getGuildFilterLabel(), function()
        local options = { "all" }
        for _, guildKey in ipairs(guildOptions) do
            options[#options + 1] = guildKey
        end
        mainFrame.state.boardFilterGuild = cycleOption(options, mainFrame.state.boardFilterGuild or "all")
        refreshBoard()
    end)

    local pinnedButton = createActionButton(leftPanel.Content, "TOPLEFT", typeButton, "BOTTOMLEFT", 0, -10, leftContentWidth, mainFrame.state.boardFilterPinnedOnly and "Pinned Only" or "All Posts", function()
        mainFrame.state.boardFilterPinnedOnly = not mainFrame.state.boardFilterPinnedOnly
        refreshBoard()
    end)

    local resultMeta = leftPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    resultMeta:SetPoint("TOPLEFT", pinnedButton, "BOTTOMLEFT", 0, -12)
    resultMeta:SetText(string.format("Showing %d visible posts", #records))
    resultMeta:SetTextColor(0.74, 0.72, 0.68, 1)

    local typeIcons = {
        announcement = NEWS_ICON_PATH,
        event = EVENT_ICON_PATH,
        resource = RESOURCE_ICON_PATH,
    }

    local listScroll = CreateFrame("ScrollFrame", nil, leftPanel.Content, "UIPanelScrollFrameTemplate")
    listScroll:SetPoint("TOPLEFT", resultMeta, "BOTTOMLEFT", 0, -10)
    listScroll:SetPoint("BOTTOMRIGHT", leftPanel.Content, "BOTTOMRIGHT", -26, 0)

    local listChild = CreateFrame("Frame", nil, listScroll)
    listChild:SetWidth(leftContentWidth - 22)
    listChild:SetHeight(1)
    listScroll:SetScrollChild(listChild)

    if #records == 0 then
        local empty = listChild:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        empty:SetPoint("TOPLEFT", listChild, "TOPLEFT", 0, -18)
        empty:SetWidth(leftContentWidth - 24)
        empty:SetJustifyH("LEFT")
        empty:SetText("No board posts match the current filters.")
        empty:SetTextColor(0.92, 0.88, 0.80, 1)
        listChild:SetHeight(80)
    end

    for index, record in ipairs(records) do
        local row = CreateFrame("Button", nil, listChild, BackdropTemplateMixin and "BackdropTemplate" or nil)
        row:SetPoint("TOPLEFT", listChild, "TOPLEFT", 0, -((index - 1) * 116))
        row:SetSize(leftContentWidth - 22, 108)
        row:SetScript("OnClick", function()
            mainFrame:SetSelectedContent(record.id)
            mainFrame:SetBoardEditing(false)
            refreshBoard()
        end)

        local rowArt = row:CreateTexture(nil, "BACKGROUND")
        rowArt:SetAllPoints(row)
        applyArtTexture(rowArt, record.id == selectedContentId and CARD_ART_KEY or PANEL_ART_KEY, { 0.10, 0.10, 0.12, 0.96 })

        local icon = row:CreateTexture(nil, "ARTWORK")
        icon:SetPoint("TOPLEFT", row, "TOPLEFT", 14, -18)
        icon:SetSize(54, 54)
        applyBadgeTexture(icon, typeIcons[record.type] or NEWS_ICON_PATH, { 0.84, 0.72, 0.44, 1 })

        local rowTitle = row:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
        rowTitle:SetPoint("TOPLEFT", icon, "TOPRIGHT", 14, -2)
        rowTitle:SetWidth(leftContentWidth - 134)
        rowTitle:SetJustifyH("LEFT")
        rowTitle:SetText(record.title or "Untitled")
        rowTitle:SetTextColor(0.98, 0.92, 0.80, 1)

        local rowMeta = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        rowMeta:SetPoint("TOPLEFT", rowTitle, "BOTTOMLEFT", 0, -8)
        rowMeta:SetText(string.format("%s  |  Guild: %s", string.lower(record.type or "announcement"), record.guildName or "Unguilded"))
        rowMeta:SetTextColor(0.76, 0.74, 0.70, 1)

        local rowDate = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        rowDate:SetPoint("TOPLEFT", rowMeta, "BOTTOMLEFT", 0, -8)
        rowDate:SetText(string.format("%s  |  By %s", formatBoardDate(record.updatedAt), record.author or "unknown"))
        rowDate:SetTextColor(0.80, 0.78, 0.74, 1)
    end

    listChild:SetHeight(math.max(1, #records * 116))

    if not selectedRecord then
        local emptyTitle = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
        emptyTitle:SetPoint("TOPLEFT", rightPanel.Content, "TOPLEFT", 0, 0)
        emptyTitle:SetText("No post selected")
        emptyTitle:SetTextColor(0.98, 0.92, 0.80, 1)
        return root
    end

    local draft = mainFrame:GetBoardDraft(selectedRecord)
    local isEditing = mainFrame.state.boardIsEditing and canManageBoard and true or false
    local rightContentWidth = rightPanel.Content:GetWidth()
    local typeFieldWidth = 190
    local guildFieldWidth = 240
    local fieldGap = 14
    local titleFieldWidth = math.max(220, rightContentWidth - typeFieldWidth - guildFieldWidth - (fieldGap * 2))
    local bodyHeight = math.max(260, rightPanel.Content:GetHeight() - 314)

    local detailIcon = rightPanel.Content:CreateTexture(nil, "ARTWORK")
    detailIcon:SetPoint("TOPLEFT", rightPanel.Content, "TOPLEFT", 0, 2)
    detailIcon:SetSize(58, 58)
    applyBadgeTexture(detailIcon, typeIcons[selectedRecord.type] or NEWS_ICON_PATH, { 0.84, 0.72, 0.44, 1 })

    local detailTitle = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
    detailTitle:SetPoint("TOPLEFT", detailIcon, "TOPRIGHT", 16, -2)
    detailTitle:SetWidth(rightContentWidth - 170)
    detailTitle:SetJustifyH("LEFT")
    detailTitle:SetText(selectedRecord.title or "Untitled")
    detailTitle:SetTextColor(0.98, 0.90, 0.64, 1)

    if selectedRecord.pinned then
        local pinnedTag = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        pinnedTag:SetPoint("TOPRIGHT", rightPanel.Content, "TOPRIGHT", -6, -10)
        pinnedTag:SetText("Pinned")
        pinnedTag:SetTextColor(0.97, 0.84, 0.48, 1)
    end

    local titleLabel = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    titleLabel:SetPoint("TOPLEFT", rightPanel.Content, "TOPLEFT", 0, -78)
    titleLabel:SetText("TITLE")
    titleLabel:SetTextColor(0.97, 0.86, 0.60, 1)

    local titleHost = createInputHost(rightPanel.Content, "TOPLEFT", titleLabel, "BOTTOMLEFT", 0, -6, titleFieldWidth, 34)

    if isEditing then
        local titleInput = CreateFrame("EditBox", nil, titleHost, "InputBoxTemplate")
        initializeInput(titleInput)
        titleInput:SetPoint("TOPLEFT", titleHost, "TOPLEFT", 10, -6)
        titleInput:SetPoint("BOTTOMRIGHT", titleHost, "BOTTOMRIGHT", -10, 6)
        titleInput:SetText(draft.title or "")
        titleInput:SetScript("OnTextChanged", function(self)
            mainFrame:GetBoardDraft(selectedRecord).title = self:GetText()
            mainFrame.state.boardDraftDirty = true
        end)
        titleInput:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    else
        local titleValue = titleHost:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        titleValue:SetPoint("LEFT", titleHost, "LEFT", 10, 0)
        titleValue:SetWidth(344)
        titleValue:SetJustifyH("LEFT")
        titleValue:SetText(selectedRecord.title or "")
        titleValue:SetTextColor(0.94, 0.91, 0.84, 1)
    end

    local typeLabel = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    typeLabel:SetPoint("TOPLEFT", titleHost, "TOPRIGHT", fieldGap, 6)
    typeLabel:SetText("TYPE")
    typeLabel:SetTextColor(0.97, 0.86, 0.60, 1)

    local guildLabel = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    guildLabel:SetPoint("TOPLEFT", typeLabel, "TOPRIGHT", typeFieldWidth + fieldGap - 26, 0)
    guildLabel:SetText("GUILD")
    guildLabel:SetTextColor(0.97, 0.86, 0.60, 1)

    if isEditing then
        createActionButton(rightPanel.Content, "TOPLEFT", typeLabel, "BOTTOMLEFT", 0, -6, typeFieldWidth, formatBoardType(draft.type), function()
            local nextValue = cycleOption(editableTypeOptions, draft.type or editableTypeOptions[1])
            mainFrame:GetBoardDraft(selectedRecord).type = nextValue
            mainFrame.state.boardDraftDirty = true
            refreshBoard()
        end)
        createActionButton(rightPanel.Content, "TOPLEFT", guildLabel, "BOTTOMLEFT", 0, -6, guildFieldWidth, draft.guildName or (guildOptions[1] or "Unguilded"), function()
            local nextValue = cycleOption(guildOptions, draft.guildName or guildOptions[1] or "Unguilded")
            mainFrame:GetBoardDraft(selectedRecord).guildName = nextValue
            mainFrame.state.boardDraftDirty = true
            refreshBoard()
        end)
    else
        local typeHost = createInputHost(rightPanel.Content, "TOPLEFT", typeLabel, "BOTTOMLEFT", 0, -6, typeFieldWidth, 34)
        local typeValue = typeHost:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        typeValue:SetPoint("LEFT", typeHost, "LEFT", 10, 0)
        typeValue:SetText(string.lower(selectedRecord.type or "announcement"))
        typeValue:SetTextColor(0.94, 0.91, 0.84, 1)

        local guildHost = createInputHost(rightPanel.Content, "TOPLEFT", guildLabel, "BOTTOMLEFT", 0, -6, guildFieldWidth, 34)
        local guildValue = guildHost:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        guildValue:SetPoint("LEFT", guildHost, "LEFT", 10, 0)
        guildValue:SetWidth(guildFieldWidth - 20)
        guildValue:SetJustifyH("LEFT")
        guildValue:SetText(selectedRecord.guildName or "Unguilded")
        guildValue:SetTextColor(0.94, 0.91, 0.84, 1)
    end

    local bodyLabel = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    bodyLabel:SetPoint("TOPLEFT", rightPanel.Content, "TOPLEFT", 0, -148)
    bodyLabel:SetText("BODY")
    bodyLabel:SetTextColor(0.97, 0.86, 0.60, 1)

    local bodyHost = createInputHost(rightPanel.Content, "TOPLEFT", bodyLabel, "BOTTOMLEFT", 0, -8, rightContentWidth, bodyHeight)

    if isEditing then
        local bodyInput = CreateFrame("EditBox", nil, bodyHost, "InputBoxTemplate")
        initializeInput(bodyInput)
        bodyInput:SetMultiLine(true)
        bodyInput:SetPoint("TOPLEFT", bodyHost, "TOPLEFT", 10, -10)
        bodyInput:SetPoint("BOTTOMRIGHT", bodyHost, "BOTTOMRIGHT", -10, 10)
        bodyInput:SetText(draft.body or "")
        bodyInput:SetScript("OnTextChanged", function(self)
            mainFrame:GetBoardDraft(selectedRecord).body = self:GetText()
            mainFrame.state.boardDraftDirty = true
        end)
        bodyInput:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
        page._bodyInput = bodyInput
    else
        local bodyText = bodyHost:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        bodyText:SetPoint("TOPLEFT", bodyHost, "TOPLEFT", 10, -10)
        bodyText:SetWidth(rightContentWidth - 24)
        bodyText:SetJustifyH("LEFT")
        bodyText:SetJustifyV("TOP")
        bodyText:SetText(selectedRecord.body or "")
        bodyText:SetTextColor(0.90, 0.88, 0.82, 1)
    end

    local footerMeta = rightPanel.Content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    footerMeta:SetPoint("TOPLEFT", bodyHost, "BOTTOMLEFT", 0, -12)
    footerMeta:SetText(string.format("By %s  |  %s  |  Revision %d", selectedRecord.author or "unknown", formatBoardDate(selectedRecord.updatedAt), selectedRecord.revision or 0))
    footerMeta:SetTextColor(0.74, 0.72, 0.68, 1)

    local actionWidth = isEditing and math.floor((rightContentWidth - 36) / 4) or math.floor((rightContentWidth - 24) / 3)
    local primaryButtonText = isEditing and "Save Post" or "Edit"
    local primaryButton = createActionButton(rightPanel.Content, "TOPLEFT", footerMeta, "BOTTOMLEFT", 0, -16, actionWidth, primaryButtonText, function()
        if isEditing then
            local currentDraft = mainFrame:GetBoardDraft(selectedRecord)
            addon.ContentService:Update(selectedRecord.id, {
                title = currentDraft.title,
                type = currentDraft.type,
                guildName = currentDraft.guildName,
                body = currentDraft.body,
            })
            mainFrame:SetBoardEditing(false)
        else
            mainFrame:SetBoardEditing(true)
        end
        refreshBoard()
    end)

    if not canManageBoard then
        primaryButton:Disable()
    end

    local pinAnchor = primaryButton
    if isEditing then
        local cancelButton = createActionButton(rightPanel.Content, "TOPLEFT", primaryButton, "TOPRIGHT", 12, 0, actionWidth, "Cancel", function()
            mainFrame:SetBoardEditing(false)
            refreshBoard()
        end)
        pinAnchor = cancelButton
    end

    local pinButton = createActionButton(rightPanel.Content, isEditing and "TOPLEFT" or "TOPLEFT", pinAnchor, isEditing and "TOPRIGHT" or "TOPRIGHT", 12, 0, actionWidth, selectedRecord.pinned and "Unpin" or "Pin", function()
        addon.ContentService:TogglePinned(selectedRecord.id)
        refreshBoard()
    end)
    if not isEditing then
        pinButton:ClearAllPoints()
        pinButton:SetPoint("TOPLEFT", primaryButton, "TOPRIGHT", 12, 0)
    end

    local deleteButton = createActionButton(rightPanel.Content, "TOPLEFT", pinButton, "TOPRIGHT", 12, 0, actionWidth, "Delete", function()
        addon.ContentService:Delete(selectedRecord.id)
        mainFrame:SetBoardEditing(false)
        mainFrame:SetSelectedContent(nil)
        refreshBoard()
    end)

    if not canManageBoard then
        pinButton:Disable()
        deleteButton:Disable()
    end

    return root
end

return page