local _, ns = ...
local L = ns.L

local OneWoW_GUI = LibStub("OneWoW_GUI-1.0", true)
if not OneWoW_GUI then return end

local BACKDROP_INNER_NO_INSETS = OneWoW_GUI.Constants.BACKDROP_INNER_NO_INSETS

ns.UI = ns.UI or {}

local selectedZone   = nil
local zoneListItems  = {}
local categoryFilter = "All"
local storageFilter  = "All"
local searchFilter   = ""
local currentSort    = { by = "name", ascending = true }

local contentEditBox  = nil
local detailPanel     = nil
local emptyMessage    = nil
local leftStatusText  = nil
local rightStatusText = nil
local scrollChild     = nil
local todoContainer       = nil
local contentUpdateTimer  = nil

local MEDIA = "Interface\\AddOns\\OneWoW_Notes\\Media\\"

local function CreateThemedPanel(name, parentFrame)
    local f = CreateFrame("Frame", name, parentFrame, "BackdropTemplate")
    f:SetBackdrop(BACKDROP_INNER_NO_INSETS)
    f:SetBackdropColor(OneWoW_GUI:GetThemeColor("BG_PRIMARY"))
    f:SetBackdropBorderColor(OneWoW_GUI:GetThemeColor("BORDER_DEFAULT"))
    return f
end

local function CreateThemedBar(name, parentFrame)
    local f = CreateFrame("Frame", name, parentFrame, "BackdropTemplate")
    f:SetBackdrop(BACKDROP_INNER_NO_INSETS)
    f:SetBackdropColor(OneWoW_GUI:GetThemeColor("BG_SECONDARY"))
    f:SetBackdropBorderColor(OneWoW_GUI:GetThemeColor("BORDER_DEFAULT"))
    return f
end

local function GetFontColorFromKey(fontColorKey, pinColorKey)
    return ns.Config:GetResolvedFontColor(fontColorKey, pinColorKey)
end

function ns.UI.CreateZonesTab(parent)
    do
        local p = OneWoW_Notes.db.global.tabSortPrefs.zones
        currentSort.by        = p.by or "name"
        currentSort.ascending = p.ascending ~= false
    end

    local controlPanel = CreateThemedBar(nil, parent)
    controlPanel:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
    controlPanel:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, 0)
    controlPanel:SetHeight(75)

    local controlTitle = OneWoW_GUI:CreateFS(controlPanel, 10)
    controlTitle:SetPoint("TOPLEFT", controlPanel, "TOPLEFT", 10, -8)
    controlTitle:SetText(L["ZONES_CONTROLS"] or "Zones Controls")
    controlTitle:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_SECONDARY"))

    local addZoneBtn = OneWoW_GUI:CreateFitTextButton(controlPanel, { text = L["BUTTON_MANUAL_ENTRY"] or "Manual Add", height = 25, minWidth = 80 })
    addZoneBtn:SetPoint("TOPLEFT", controlPanel, "TOPLEFT", 10, -28)
    addZoneBtn:SetScript("OnClick", function()
        ns.UI.ShowManualZoneEntryDialog(parent)
    end)

    local detectBtn = OneWoW_GUI:CreateFitTextButton(controlPanel, { text = L["BUTTON_ADD_CURRENT_ZONE"] or "Add Sub", height = 25, minWidth = 80 })
    detectBtn:SetPoint("LEFT", addZoneBtn, "RIGHT", 6, 0)
    detectBtn:SetScript("OnClick", function()
        if not ns.Zones then return end
        local zoneName = ns.Zones:GetCurrentZoneName()
        if not zoneName or zoneName == "" then
            print("|cFFFFD100OneWoW - Zones:|r " .. (L["ZONE_DETECT_FAIL"] or "Could not detect zone."))
            return
        end
        if ns.Zones:GetZone(zoneName) then
            selectedZone = zoneName
            if parent.SelectZone then parent.SelectZone(zoneName) end
            print("|cFFFFD100OneWoW - Zones:|r " .. string.format(L["MSG_ZONE_EXISTS"] or "Zone exists: %s", zoneName))
            return
        end
        local mapInfo = ns.Zones:GetCurrentMapInfo()
        local zoneData = { content = "", category = "General", storage = "account", pinColor = "sync", fontColor = "match" }
        if mapInfo then
            zoneData.mapID = mapInfo.mapID
            zoneData.parentMapID = mapInfo.parentMapID
        end
        ns.Zones:AddZone(zoneName, zoneData)
        selectedZone = zoneName
        parent.RefreshZonesList()
        if parent.SelectZone then parent.SelectZone(zoneName) end
        print("|cFFFFD100OneWoW - Zones:|r " .. string.format(L["MSG_ZONE_ADDED"] or "Added: %s", zoneName))
    end)

    local addParentBtn = OneWoW_GUI:CreateFitTextButton(controlPanel, { text = L["ZONE_ADD_PARENT"] or "Add Parent", height = 25, minWidth = 80 })
    addParentBtn:SetPoint("LEFT", detectBtn, "RIGHT", 6, 0)
    addParentBtn:SetScript("OnClick", function()
        if not ns.Zones then return end
        local parentZoneName = ns.Zones:GetParentZoneName()
        if not parentZoneName or parentZoneName == "" then
            print("|cFFFFD100OneWoW - Zones:|r " .. (L["MSG_NO_PARENT_ZONE"] or "Could not detect parent zone."))
            return
        end
        if ns.Zones:GetZone(parentZoneName) then
            selectedZone = parentZoneName
            if parent.SelectZone then parent.SelectZone(parentZoneName) end
            print("|cFFFFD100OneWoW - Zones:|r " .. string.format(L["MSG_ZONE_EXISTS"] or "Zone exists: %s", parentZoneName))
            return
        end
        local mapInfo = ns.Zones:GetCurrentMapInfo()
        local zoneData = { content = "", category = "General", storage = "account", pinColor = "sync", fontColor = "match" }
        if mapInfo and mapInfo.parentMapID and mapInfo.parentMapID > 0 then
            local parentMapInfo = C_Map.GetMapInfo(mapInfo.parentMapID)
            if parentMapInfo then
                zoneData.mapID = mapInfo.parentMapID
                zoneData.parentMapID = parentMapInfo.parentMapID
            end
        elseif mapInfo then
            zoneData.mapID = mapInfo.mapID
            zoneData.parentMapID = mapInfo.parentMapID
        end
        ns.Zones:AddZone(parentZoneName, zoneData)
        selectedZone = parentZoneName
        parent.RefreshZonesList()
        if parent.SelectZone then parent.SelectZone(parentZoneName) end
        print("|cFFFFD100OneWoW - Zones:|r " .. string.format(L["MSG_ZONE_ADDED"] or "Added: %s", parentZoneName))
    end)

    local categoryDropdown = ns.UI.CreateThemedDropdown(controlPanel, L["LABEL_CATEGORY"], 140, 25)
    categoryDropdown:SetPoint("LEFT", addParentBtn, "RIGHT", 8, 0)
    local function RefreshCatOpts()
        local catOpts = {{text = L["UI_ALL"] or "All", value = "All"}}
        if ns.Zones then
            for _, c in ipairs(ns.Zones:GetCategories()) do
                catOpts[#catOpts + 1] = {text = c, value = c}
            end
        end
        categoryDropdown:SetOptions(catOpts)
        categoryDropdown:SetSelected(categoryFilter)
    end
    RefreshCatOpts()
    categoryDropdown.onSelect = function(value)
        categoryFilter = value
        parent.RefreshZonesList()
    end

    local manageCategoriesBtn = CreateFrame("Button", nil, controlPanel)
    manageCategoriesBtn:SetSize(20, 20)
    manageCategoriesBtn:SetPoint("LEFT", categoryDropdown, "RIGHT", 4, 0)
    manageCategoriesBtn:SetNormalTexture(MEDIA .. "icon-gears.png")
    manageCategoriesBtn:GetNormalTexture():SetTexCoord(0.1, 0.9, 0.1, 0.9)
    manageCategoriesBtn:SetHighlightTexture(MEDIA .. "icon-gears.png")
    manageCategoriesBtn:GetHighlightTexture():SetTexCoord(0.1, 0.9, 0.1, 0.9)
    manageCategoriesBtn:GetHighlightTexture():SetAlpha(0.5)
    manageCategoriesBtn:SetScript("OnClick", function()
        if ns.UI and ns.UI.ShowCategoryManager then
            ns.UI.ShowCategoryManager("zones")
        end
    end)
    manageCategoriesBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(L["UI_MANAGE_CATEGORIES"] or "Manage Categories", 1, 1, 1)
        GameTooltip:AddLine(L["UI_MANAGE_CATEGORIES_DESC"] or "Add, remove, and organize categories.", 0.8, 0.8, 0.8, true)
        GameTooltip:Show()
    end)
    manageCategoriesBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

    local storageDropdown = ns.UI.CreateThemedDropdown(controlPanel, L["LABEL_STORAGE"], 130, 25)
    storageDropdown:SetPoint("LEFT", manageCategoriesBtn, "RIGHT", 4, 0)
    storageDropdown:SetOptions({
        {text = L["UI_ALL"] or "All",                     value = "All"},
        {text = L["UI_STORAGE_ACCOUNT"] or "Account",     value = "account"},
        {text = L["UI_STORAGE_CHARACTER"] or "Character", value = "character"},
    })
    storageDropdown:SetSelected("All")
    storageDropdown.onSelect = function(value)
        storageFilter = value
        parent.RefreshZonesList()
    end

    local zoneSortHandle = OneWoW_GUI:CreateSortControls(controlPanel, {
        sortFields = {
            {key = "name",     label = L["NOTE_SORT_NAME"]},
            {key = "category", label = L["NOTE_SORT_CATEGORY"]},
            {key = "color",    label = L["NOTE_SORT_COLOR"]},
            {key = "manual",   label = L["NOTE_SORT_MANUAL"]},
        },
        defaultField  = currentSort.by,
        defaultAsc    = currentSort.ascending,
        dropdownWidth = 100,
        onChange = function(field, ascending)
            currentSort.by        = field
            currentSort.ascending = ascending
            OneWoW_Notes.db.global.tabSortPrefs.zones = { by = field, ascending = ascending }
            parent.RefreshZonesList()
        end,
    })
    zoneSortHandle.dropdown:SetPoint("LEFT", storageDropdown, "RIGHT", 6, 0)
    zoneSortHandle.dirBtn:SetPoint("LEFT", zoneSortHandle.dropdown, "RIGHT", 4, 0)

    local helpButton = CreateFrame("Button", nil, controlPanel)
    helpButton:SetSize(28, 28)
    helpButton:SetPoint("TOPRIGHT", controlPanel, "TOPRIGHT", -10, -10)
    local helpIcon = helpButton:CreateTexture(nil, "ARTWORK")
    helpIcon:SetSize(24, 24)
    helpIcon:SetPoint("CENTER", helpButton, "CENTER", 0, 0)
    helpIcon:SetAtlas("CampaignActiveQuestIcon")
    helpButton:SetScript("OnClick", function()
        if not ns.UI.notesHelpPanel and ns.UI.CreateNotesHelpPanel then
            ns.UI.notesHelpPanel = ns.UI.CreateNotesHelpPanel()
        end
        if ns.UI.notesHelpPanel then
            if ns.UI.notesHelpPanel:IsShown() then
                ns.UI.notesHelpPanel:Hide()
            else
                ns.UI.notesHelpPanel:Show()
            end
        end
    end)
    helpButton:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText(L["UI_ZONES_HELP_TITLE"] or "Zones Help", 1, 1, 1)
        GameTooltip:AddLine(L["UI_ZONES_HELP_HINT"] or "Click for zones help.", 0.8, 0.8, 0.8, true)
        GameTooltip:Show()
    end)
    helpButton:SetScript("OnLeave", function() GameTooltip:Hide() end)

    local listingPanel = CreateThemedPanel(nil, parent)
    listingPanel:SetPoint("TOPLEFT", controlPanel, "BOTTOMLEFT", 0, -10)
    listingPanel:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 0, 35)
    listingPanel:SetWidth(258)

    local listingTitle = OneWoW_GUI:CreateFS(listingPanel, 16)
    listingTitle:SetPoint("TOP", listingPanel, "TOP", 0, -10)
    listingTitle:SetText(L["TAB_ZONES"] or "Zones")
    listingTitle:SetTextColor(OneWoW_GUI:GetThemeColor("ACCENT_PRIMARY"))

    local searchBox = OneWoW_GUI:CreateEditBox(listingPanel, {
        placeholderText = L["UI_SEARCH_PLACEHOLDER"],
        onTextChanged = function(text)
            searchFilter = text
            if parent.RefreshZonesList then parent.RefreshZonesList() end
        end,
    })
    searchBox:SetPoint("TOPLEFT",  listingPanel, "TOPLEFT",  8, -30)
    searchBox:SetPoint("TOPRIGHT", listingPanel, "TOPRIGHT", -8, -30)

    local listScroll = ns.UI.CreateCustomScroll(listingPanel)
    scrollChild = listScroll.scrollChild
    listScroll.container:SetPoint("TOPLEFT", listingPanel, "TOPLEFT", 10, -62)
    listScroll.container:SetPoint("BOTTOMRIGHT", listingPanel, "BOTTOMRIGHT", -10, 10)

    detailPanel = CreateThemedPanel(nil, parent)
    detailPanel:SetPoint("TOPLEFT", listingPanel, "TOPRIGHT", 10, 0)
    detailPanel:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 35)

    emptyMessage = OneWoW_GUI:CreateFS(detailPanel, 16)
    emptyMessage:SetPoint("CENTER", detailPanel, "CENTER")
    emptyMessage:SetText(L["ZONES_SELECT_PROMPT"] or "Select a zone to view its content")
    emptyMessage:SetTextColor(0.6, 0.6, 0.7, 1)

    local leftStatusBar = CreateThemedBar(nil, parent)
    leftStatusBar:SetPoint("TOPLEFT", listingPanel, "BOTTOMLEFT", 0, -5)
    leftStatusBar:SetPoint("TOPRIGHT", listingPanel, "BOTTOMRIGHT", 0, -5)
    leftStatusBar:SetHeight(25)

    leftStatusText = OneWoW_GUI:CreateFS(leftStatusBar, 10)
    leftStatusText:SetPoint("LEFT", leftStatusBar, "LEFT", 10, 0)
    leftStatusText:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_SECONDARY"))
    leftStatusText:SetText(string.format(L["UI_COUNT_FORMAT"], L["TAB_ZONES"], 0))

    local rightStatusBar = CreateThemedBar(nil, parent)
    rightStatusBar:SetPoint("TOPLEFT", detailPanel, "BOTTOMLEFT", 0, -5)
    rightStatusBar:SetPoint("TOPRIGHT", detailPanel, "BOTTOMRIGHT", 0, -5)
    rightStatusBar:SetHeight(25)

    rightStatusText = OneWoW_GUI:CreateFS(rightStatusBar, 10)
    rightStatusText:SetPoint("LEFT", rightStatusBar, "LEFT", 10, 0)
    rightStatusText:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_SECONDARY"))
    rightStatusText:SetText(L["STATUS_READY"])

    local function ShowEditor()
        emptyMessage:Hide()

        for _, child in ipairs({detailPanel:GetChildren()}) do
            child:Hide()
        end

        if not detailPanel.editorContent then
            local editorHeader = CreateThemedBar(nil, detailPanel)
            editorHeader:SetPoint("TOPLEFT", detailPanel, "TOPLEFT", 10, -10)
            editorHeader:SetPoint("TOPRIGHT", detailPanel, "TOPRIGHT", -10, -10)
            editorHeader:SetHeight(85)

            local zoneTitleFS = OneWoW_GUI:CreateFS(editorHeader, 16)
            zoneTitleFS:SetPoint("TOPLEFT", editorHeader, "TOPLEFT", 12, -8)
            zoneTitleFS:SetPoint("TOPRIGHT", editorHeader, "TOPRIGHT", -110, -8)
            zoneTitleFS:SetJustifyH("LEFT")
            zoneTitleFS:SetWordWrap(false)
            zoneTitleFS:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_PRIMARY"))
            editorHeader.zoneTitleFS = zoneTitleFS

            local deleteBtn = CreateFrame("Button", nil, editorHeader)
            deleteBtn:SetSize(22, 22)
            deleteBtn:SetPoint("TOPRIGHT", editorHeader, "TOPRIGHT", -12, -12)
            deleteBtn:SetNormalTexture(MEDIA .. "icon-trash.png")
            deleteBtn:SetPushedTexture(MEDIA .. "icon-trash.png")
            deleteBtn:SetHighlightTexture(MEDIA .. "icon-trash.png")
            deleteBtn:GetHighlightTexture():SetAlpha(0.5)
            deleteBtn:SetScript("OnClick", function()
                if selectedZone then
                    local zName = selectedZone
                    local confirmResult = OneWoW_GUI:CreateConfirmDialog({
                        name = "OneWoW_NotesDeleteZoneConfirm",
                        title = L["DIALOG_CONFIRM_DELETE"] or "Confirm Delete",
                        message = string.format(L["ZONE_CONFIRM_DELETE"] or "Delete zone: %s?", zName),
                        buttons = {
                            {
                                text = L["BUTTON_DELETE"] or "Delete",
                                color = {0.8, 0.2, 0.2},
                                onClick = function(dlg)
                                    if ns.ZonePins then ns.ZonePins:DestroyZonePin(zName) end
                                    if ns.Zones then ns.Zones:RemoveZone(zName) end
                                    selectedZone = nil
                                    if detailPanel.editorContent then
                                        for _, frame in pairs(detailPanel.editorContent) do
                                            if frame and frame.Hide then frame:Hide() end
                                        end
                                    end
                                    emptyMessage:Show()
                                    parent.RefreshZonesList()
                                    dlg:Hide()
                                end,
                            },
                            { text = L["BUTTON_CANCEL"] or "Cancel", onClick = function(dlg) dlg:Hide() end },
                        },
                    })
                    confirmResult.frame:Show()
                end
            end)
            deleteBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["TOOLTIP_NOTE_DELETE"] or "Delete", 1, 1, 1)
                GameTooltip:AddLine(L["TOOLTIP_NOTE_DELETE_DESC"] or "Delete this zone.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            deleteBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

            local propertiesBtn = CreateFrame("Button", nil, editorHeader)
            propertiesBtn:SetSize(22, 22)
            propertiesBtn:SetPoint("RIGHT", deleteBtn, "LEFT", -2, 0)
            propertiesBtn:SetNormalTexture(MEDIA .. "icon-gears.png")
            propertiesBtn:SetPushedTexture(MEDIA .. "icon-gears.png")
            propertiesBtn:SetHighlightTexture(MEDIA .. "icon-gears.png")
            propertiesBtn:GetHighlightTexture():SetAlpha(0.5)
            propertiesBtn:SetScript("OnClick", function()
                if selectedZone then
                    ns.UI.ShowZonePropertiesDialog(selectedZone, parent)
                end
            end)
            propertiesBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["TOOLTIP_NOTE_PROPERTIES"] or "Properties", 1, 1, 1)
                GameTooltip:AddLine(L["TOOLTIP_NOTE_PROPERTIES_DESC"] or "Edit zone properties.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            propertiesBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

            local pinBtn = CreateFrame("CheckButton", nil, editorHeader)
            pinBtn:SetSize(22, 22)
            pinBtn:SetPoint("RIGHT", propertiesBtn, "LEFT", -2, 0)

            local pinNormalTex = pinBtn:CreateTexture(nil, "BACKGROUND")
            pinNormalTex:SetAllPoints()
            pinNormalTex:SetTexture(MEDIA .. "icon-pin.png")
            pinNormalTex:SetDesaturated(true)
            pinNormalTex:SetAlpha(0.3)
            pinBtn:SetNormalTexture(pinNormalTex)

            local pinHighlightTex = pinBtn:CreateTexture(nil, "HIGHLIGHT")
            pinHighlightTex:SetAllPoints()
            pinHighlightTex:SetTexture(MEDIA .. "icon-pin.png")
            pinHighlightTex:SetAlpha(0.5)
            pinBtn:SetHighlightTexture(pinHighlightTex)

            pinBtn:SetScript("OnClick", function(self)
                if selectedZone and ns.ZonePins and ns.Zones then
                    local zoneData = ns.Zones:GetZone(selectedZone)
                    if zoneData then
                        if zoneData.pinEnabled and OneWoW_Notes.zonePins and OneWoW_Notes.zonePins[selectedZone] then
                            ns.ZonePins:HideZonePin(selectedZone)
                            zoneData.pinEnabled = false
                            self:GetNormalTexture():SetDesaturated(true)
                            self:GetNormalTexture():SetAlpha(0.3)
                            self:SetChecked(false)
                        else
                            zoneData.pinEnabled = true
                            ns.ZonePins:ShowZonePin(selectedZone, zoneData)
                            self:GetNormalTexture():SetDesaturated(false)
                            self:GetNormalTexture():SetAlpha(1.0)
                            self:SetChecked(true)
                        end
                        parent.RefreshZonesList()
                    end
                end
            end)
            pinBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["TOOLTIP_NOTE_PIN"] or "Pin", 1, 1, 1)
                GameTooltip:AddLine(L["TOOLTIP_NOTE_PIN_DESC"] or "Pin this zone.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            pinBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            editorHeader.pinBtn = pinBtn

            local alertHeaderBtn = CreateFrame("CheckButton", nil, editorHeader)
            alertHeaderBtn:SetSize(22, 22)
            alertHeaderBtn:SetPoint("RIGHT", pinBtn, "LEFT", -2, 0)

            local alertHNormalTex = alertHeaderBtn:CreateTexture(nil, "BACKGROUND")
            alertHNormalTex:SetAllPoints()
            alertHNormalTex:SetTexture(MEDIA .. "icon-alert.png")
            alertHNormalTex:SetDesaturated(true)
            alertHNormalTex:SetAlpha(0.3)
            alertHeaderBtn:SetNormalTexture(alertHNormalTex)

            local alertHHighlightTex = alertHeaderBtn:CreateTexture(nil, "HIGHLIGHT")
            alertHHighlightTex:SetAllPoints()
            alertHHighlightTex:SetTexture(MEDIA .. "icon-alert.png")
            alertHHighlightTex:SetAlpha(0.5)
            alertHeaderBtn:SetHighlightTexture(alertHHighlightTex)

            alertHeaderBtn:SetScript("OnClick", function(self)
                if selectedZone and ns.Zones then
                    local zoneData = ns.Zones:GetZone(selectedZone)
                    if zoneData then
                        local wasEnabled = zoneData.alertEnabled ~= false
                        zoneData.alertEnabled = not wasEnabled
                        if zoneData.alertEnabled then
                            self:GetNormalTexture():SetDesaturated(false)
                            self:GetNormalTexture():SetAlpha(1.0)
                            self:SetChecked(true)
                        else
                            self:GetNormalTexture():SetDesaturated(true)
                            self:GetNormalTexture():SetAlpha(0.3)
                            self:SetChecked(false)
                        end
                        ns.Zones:SaveZone(selectedZone, zoneData)
                        parent.RefreshZonesList()
                    end
                end
            end)
            alertHeaderBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["TOOLTIP_ZONE_ALERT"] or "Zone Alert", 1, 1, 1)
                GameTooltip:AddLine(L["TOOLTIP_ZONE_ALERT_DESC"] or "Toggle zone entry alert.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            alertHeaderBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            editorHeader.alertBtn = alertHeaderBtn

            local favoriteBtn = CreateFrame("CheckButton", nil, editorHeader)
            favoriteBtn:SetSize(22, 22)
            favoriteBtn:SetPoint("RIGHT", alertHeaderBtn, "LEFT", -2, 0)

            local favNormalTex = favoriteBtn:CreateTexture(nil, "BACKGROUND")
            favNormalTex:SetAllPoints()
            favNormalTex:SetTexture(MEDIA .. "icon-fav.png")
            favNormalTex:SetDesaturated(true)
            favNormalTex:SetAlpha(0.3)
            favoriteBtn:SetNormalTexture(favNormalTex)

            local favCheckedTex = favoriteBtn:CreateTexture(nil, "BACKGROUND")
            favCheckedTex:SetAllPoints()
            favCheckedTex:SetTexture(MEDIA .. "icon-fav.png")
            favoriteBtn:SetCheckedTexture(favCheckedTex)

            local favHighlightTex = favoriteBtn:CreateTexture(nil, "HIGHLIGHT")
            favHighlightTex:SetAllPoints()
            favHighlightTex:SetTexture(MEDIA .. "icon-fav.png")
            favHighlightTex:SetAlpha(0.5)
            favoriteBtn:SetHighlightTexture(favHighlightTex)

            favoriteBtn:SetScript("OnClick", function(self)
                if selectedZone and ns.Zones then
                    local zoneData = ns.Zones:GetZone(selectedZone)
                    if zoneData then
                        zoneData.favorite = not zoneData.favorite
                        if zoneData.favorite then
                            self:GetNormalTexture():SetDesaturated(false)
                            self:GetNormalTexture():SetAlpha(1.0)
                            self:SetChecked(true)
                        else
                            self:GetNormalTexture():SetDesaturated(true)
                            self:GetNormalTexture():SetAlpha(0.3)
                            self:SetChecked(false)
                        end
                        ns.Zones:SaveZone(selectedZone, zoneData)
                        parent.RefreshZonesList()
                    end
                end
            end)
            favoriteBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["TOOLTIP_NOTE_FAVORITE"] or "Favorite", 1, 1, 1)
                GameTooltip:AddLine(L["TOOLTIP_NOTE_FAVORITE_DESC"] or "Mark as favorite.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            favoriteBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            editorHeader.favoriteBtn = favoriteBtn

            local categoryLine = OneWoW_GUI:CreateFS(editorHeader, 10)
            categoryLine:SetPoint("BOTTOMLEFT", editorHeader, "BOTTOMLEFT", 12, 8)
            categoryLine:SetTextColor(OneWoW_GUI:GetThemeColor("ACCENT_PRIMARY"))
            categoryLine:SetJustifyH("LEFT")
            editorHeader.categoryLine = categoryLine

            local mapLine = OneWoW_GUI:CreateFS(editorHeader, 10)
            mapLine:SetPoint("BOTTOMRIGHT", editorHeader, "BOTTOMRIGHT", -12, 8)
            mapLine:SetTextColor(OneWoW_GUI:GetThemeColor("ACCENT_PRIMARY"))
            mapLine:SetJustifyH("RIGHT")
            editorHeader.mapLine = mapLine

            local contentBg = CreateThemedBar(nil, detailPanel)
            contentBg:SetPoint("TOPLEFT", editorHeader, "BOTTOMLEFT", 0, -10)
            contentBg:SetPoint("TOPRIGHT", editorHeader, "BOTTOMRIGHT", 0, -10)
            contentBg:SetHeight(190)
            contentBg:EnableMouse(true)

            local contentScroll = OneWoW_GUI:CreateScrollFrame(contentBg, {})
            contentScroll:SetPoint("TOPLEFT", contentBg, "TOPLEFT", 4, -4)
            contentScroll:SetPoint("BOTTOMRIGHT", contentBg, "BOTTOMRIGHT", -26, 4)
            contentBg:SetFrameLevel(contentScroll:GetFrameLevel() - 1)

            contentEditBox = CreateFrame("EditBox", nil, contentScroll)
            contentEditBox:SetMultiLine(true)
            contentEditBox:SetFontObject("ChatFontNormal")
            contentEditBox:SetWidth(contentScroll:GetWidth() - 20)
            contentEditBox:SetAutoFocus(false)
            contentEditBox:SetMaxLetters(0)
            contentEditBox:SetHyperlinksEnabled(true)
            contentEditBox:SetScript("OnHyperlinkClick", function(_, link, text, button)
                SetItemRef(link, text, button)
            end)
            contentEditBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
            contentEditBox:SetScript("OnTextChanged", function(self, userInput)
                if userInput and selectedZone and ns.Zones then
                    local d = ns.Zones:GetZone(selectedZone)
                    if d then
                        d.content = self:GetText()
                        d.modified = GetServerTime()
                        ns.Zones:SaveZone(selectedZone, d)

                        if contentUpdateTimer then contentUpdateTimer:Cancel() end
                        contentUpdateTimer = C_Timer.NewTimer(2, function()
                            if selectedZone and OneWoW_Notes.zonePins and OneWoW_Notes.zonePins[selectedZone] then
                                local pinFrame = OneWoW_Notes.zonePins[selectedZone]
                                if pinFrame and pinFrame.contentText then
                                    local zone = ns.Zones:GetZone(selectedZone)
                                    if zone then
                                        pinFrame.contentText:SetText(zone.content or "")
                                    end
                                end
                            end
                            contentUpdateTimer = nil
                        end)
                    end
                end
            end)
            contentEditBox:SetScript("OnReceiveDrag", function(self)
                local cursorType, _, itemLink = GetCursorInfo()
                if cursorType == "item" and itemLink then
                    self:Insert(itemLink)
                    ClearCursor()
                end
            end)
            contentEditBox:SetScript("OnMouseUp", function(self, button)
                if button == "RightButton" and ns.NotesContextMenu then
                    ns.NotesContextMenu:ShowEditBoxContextMenu(self)
                end
            end)
            if ns.NotesHyperlinks then
                ns.NotesHyperlinks:EnhanceEditBox(contentEditBox)
            end
            contentEditBox._skipGlobalFont = true
            contentScroll:SetScrollChild(contentEditBox)
            detailPanel.contentEditBox = contentEditBox

            contentBg:SetScript("OnMouseDown", function(_, button)
                if detailPanel.contentEditBox then
                    detailPanel.contentEditBox:SetFocus()
                    if button == "LeftButton" then
                        local cursorType, _, itemLink = GetCursorInfo()
                        if cursorType == "item" and itemLink then
                            detailPanel.contentEditBox:Insert(itemLink)
                            ClearCursor()
                        elseif cursorType == "spell" then
                            local spellID = select(2, GetCursorInfo())
                            if spellID then
                                local spellLink = C_Spell.GetSpellLink(spellID)
                                if spellLink then detailPanel.contentEditBox:Insert(spellLink) end
                            end
                            ClearCursor()
                        end
                    elseif button == "RightButton" and ns.NotesContextMenu then
                        ns.NotesContextMenu:ShowEditBoxContextMenu(detailPanel.contentEditBox)
                    end
                end
            end)

            local todoSection = CreateFrame("Frame", nil, detailPanel)
            todoSection:SetPoint("TOPLEFT", contentBg, "BOTTOMLEFT", 0, -10)
            todoSection:SetPoint("BOTTOMRIGHT", detailPanel, "BOTTOMRIGHT", -8, 10)
            todoSection:SetClipsChildren(true)

            local todoHeader = CreateFrame("Frame", nil, todoSection)
            todoHeader:SetPoint("TOPLEFT", todoSection, "TOPLEFT", 0, 0)
            todoHeader:SetPoint("TOPRIGHT", todoSection, "TOPRIGHT", -22, 0)
            todoHeader:SetHeight(30)

            local todoLabel = OneWoW_GUI:CreateFS(todoHeader, 12)
            todoLabel:SetPoint("LEFT", todoHeader, "LEFT", 5, 0)
            todoLabel:SetText(L["ZONE_TODO_HEADER"] or "Checklist")
            todoLabel:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_PRIMARY"))

            local resetTasksBtn = CreateFrame("Button", nil, todoHeader)
            resetTasksBtn:SetSize(20, 20)
            resetTasksBtn:SetPoint("LEFT", todoLabel, "RIGHT", 5, 0)
            resetTasksBtn:SetNormalAtlas("talents-button-undo")
            resetTasksBtn:SetPushedAtlas("talents-button-undo")
            resetTasksBtn:SetHighlightAtlas("talents-button-undo")
            resetTasksBtn:GetHighlightTexture():SetAlpha(0.5)
            resetTasksBtn:SetScript("OnClick", function()
                if selectedZone and ns.Zones then
                    local d = ns.Zones:GetZone(selectedZone)
                    if d and d.todos then
                        for _, todo in ipairs(d.todos) do
                            todo.done = false
                        end
                        ns.Zones:SaveZone(selectedZone, d)
                        if parent.RefreshZoneTodos then parent.RefreshZoneTodos() end
                    end
                end
            end)
            resetTasksBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_TOP")
                GameTooltip:SetText(L["NOTE_RESET_TODOS"] or "Reset Tasks", 1, 1, 1)
                GameTooltip:AddLine(L["NOTE_RESET_TODOS_DESC"] or "Uncheck all tasks.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            resetTasksBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

            local addTaskBtn = CreateFrame("Button", nil, todoHeader)
            addTaskBtn:SetSize(24, 24)
            addTaskBtn:SetPoint("RIGHT", todoHeader, "RIGHT", 0, 0)

            local taskInputBox = OneWoW_GUI:CreateEditBox(todoHeader, {
                height = 25,
                placeholderText = "",
            })
            taskInputBox:SetPoint("LEFT", resetTasksBtn, "RIGHT", 5, 0)
            taskInputBox:SetPoint("RIGHT", addTaskBtn, "LEFT", -5, 0)
            taskInputBox:SetScript("OnEnterPressed", function(self)
                local text = self:GetText()
                if text and text ~= "" and selectedZone and ns.Zones then
                    local d = ns.Zones:GetZone(selectedZone)
                    if d then
                        d.todos = d.todos or {}
                        table.insert(d.todos, { text = text, done = false })
                        ns.Zones:SaveZone(selectedZone, d)
                        self:SetText("")
                        if parent.RefreshZoneTodos then parent.RefreshZoneTodos() end
                    end
                end
                self:ClearFocus()
            end)
            addTaskBtn:SetNormalTexture(MEDIA .. "icon-add.png")
            addTaskBtn:SetHighlightTexture(MEDIA .. "icon-add.png")
            addTaskBtn:SetPushedTexture(MEDIA .. "icon-add.png")
            addTaskBtn:GetHighlightTexture():SetAlpha(0.5)
            addTaskBtn:SetScript("OnClick", function()
                local text = taskInputBox:GetText()
                if text and text ~= "" and selectedZone and ns.Zones then
                    local d = ns.Zones:GetZone(selectedZone)
                    if d then
                        d.todos = d.todos or {}
                        table.insert(d.todos, { text = text, done = false })
                        ns.Zones:SaveZone(selectedZone, d)
                        taskInputBox:SetText("")
                        if parent.RefreshZoneTodos then parent.RefreshZoneTodos() end
                    end
                end
            end)

            local todoScroll, todoScrollChild = OneWoW_GUI:CreateScrollFrame(todoSection, {})
            todoScroll:SetPoint("TOPLEFT", todoHeader, "BOTTOMLEFT", 0, -5)
            todoScroll:SetPoint("BOTTOMRIGHT", todoSection, "BOTTOMRIGHT", -22, 0)

            todoContainer = todoScrollChild
            detailPanel.todoContainer = todoContainer

            todoScroll:SetScript("OnSizeChanged", function(_, width)
                if todoContainer then todoContainer:SetWidth(width - 20) end
            end)

            local separatorLine = OneWoW_GUI:CreateDivider(detailPanel, { yOffset = 0 })
            separatorLine:ClearAllPoints()
            separatorLine:SetPoint("TOPLEFT", contentBg, "BOTTOMLEFT", 0, -5)
            separatorLine:SetPoint("TOPRIGHT", contentBg, "BOTTOMRIGHT", 0, -5)

            detailPanel.editorContent = {
                header        = editorHeader,
                contentScroll = contentScroll,
                contentBg     = contentBg,
                todoSection   = todoSection,
                separatorLine = separatorLine,
            }
        end

        for _, frame in pairs(detailPanel.editorContent) do
            if frame and frame.Show then frame:Show() end
        end
        ns.UI.activeContentEditBox = detailPanel.contentEditBox

        if selectedZone and ns.Zones then
            local zoneData = ns.Zones:GetZone(selectedZone)
            if zoneData and type(zoneData) == "table" then
                local header = detailPanel.editorContent.header

                if header.zoneTitleFS then
                    header.zoneTitleFS:SetText(selectedZone)
                end

                if detailPanel.contentEditBox then
                    detailPanel.contentEditBox:SetText(zoneData.content or "")
                end

                local pinColor  = zoneData.pinColor or "sync"
                local fontColor = zoneData.fontColor or "match"
                local fontSize  = zoneData.fontSize or 12

                local colorConfig = ns.Config:GetResolvedColorConfig(pinColor)
                local bgColor     = colorConfig.background
                local listItemColor = colorConfig.listItem
                local borderColor = colorConfig.border

                if detailPanel.editorContent.contentBg then
                    detailPanel.editorContent.contentBg:SetBackdropColor(bgColor[1], bgColor[2], bgColor[3], zoneData.opacity or 0.9)
                    detailPanel.editorContent.contentBg:SetBackdropBorderColor(borderColor[1], borderColor[2], borderColor[3], 1)
                end

                if detailPanel.contentEditBox then
                    local textColor = GetFontColorFromKey(fontColor, pinColor)
                    detailPanel.contentEditBox:SetTextColor(textColor[1], textColor[2], textColor[3], 1)
                    local fontPath = ns.Config:ResolveFontPath(zoneData.fontFamily)
                    detailPanel.contentEditBox:SetFont(fontPath, fontSize, zoneData.fontOutline or "")
                end

                if header then
                    local textColor = GetFontColorFromKey(fontColor, pinColor)
                    header:SetBackdropColor(listItemColor[1], listItemColor[2], listItemColor[3], listItemColor[4] or 0.9)
                    header:SetBackdropBorderColor(borderColor[1], borderColor[2], borderColor[3], 1)

                    if header.categoryLine then
                        header.categoryLine:SetTextColor(textColor[1], textColor[2], textColor[3])
                        local catText = zoneData.category or "General"
                        local storeText = zoneData.storage == "character" and (L["STORAGE_TYPE_CHARACTER"] or "Character") or (L["STORAGE_ACCOUNT_WIDE"] or "Account")
                        header.categoryLine:SetText(catText .. "  |  " .. storeText)
                    end

                    if header.mapLine then
                        header.mapLine:SetTextColor(textColor[1], textColor[2], textColor[3])
                        if zoneData.mapID then
                            local mapInfo = C_Map.GetMapInfo(zoneData.mapID)
                            if mapInfo then
                                header.mapLine:SetText("Map: " .. mapInfo.name .. " (" .. zoneData.mapID .. ")")
                            else
                                header.mapLine:SetText("Map ID: " .. zoneData.mapID)
                            end
                        else
                            header.mapLine:SetText("")
                        end
                    end

                    if header.favoriteBtn then
                        if zoneData.favorite then
                            header.favoriteBtn:GetNormalTexture():SetDesaturated(false)
                            header.favoriteBtn:GetNormalTexture():SetAlpha(1.0)
                            header.favoriteBtn:SetChecked(true)
                        else
                            header.favoriteBtn:GetNormalTexture():SetDesaturated(true)
                            header.favoriteBtn:GetNormalTexture():SetAlpha(0.3)
                            header.favoriteBtn:SetChecked(false)
                        end
                    end

                    if header.pinBtn then
                        local pinEnabled = zoneData.pinEnabled
                        header.pinBtn:GetNormalTexture():SetDesaturated(not pinEnabled)
                        header.pinBtn:GetNormalTexture():SetAlpha(pinEnabled and 1.0 or 0.3)
                        header.pinBtn:SetChecked(pinEnabled and true or false)
                    end

                    if header.alertBtn then
                        local alertEnabled = zoneData.alertEnabled ~= false
                        header.alertBtn:GetNormalTexture():SetDesaturated(not alertEnabled)
                        header.alertBtn:GetNormalTexture():SetAlpha(alertEnabled and 1.0 or 0.3)
                        header.alertBtn:SetChecked(alertEnabled)
                    end
                end

                if parent.RefreshZoneTodos then parent.RefreshZoneTodos() end
            end
        end
    end

    parent.SelectZone = function(zoneName)
        selectedZone = zoneName
        ShowEditor()
        parent.RefreshZonesList()
    end

    function parent.RefreshZoneTodos()
        if not todoContainer or not selectedZone then return end

        for _, child in ipairs({todoContainer:GetChildren()}) do
            child:Hide()
        end

        if not ns.Zones then return end
        local zoneData = ns.Zones:GetZone(selectedZone)
        if not zoneData or not zoneData.todos then return end

        local yOffset = 0
        for i, todo in ipairs(zoneData.todos) do
            local todoFrame = CreateFrame("Frame", nil, todoContainer)
            todoFrame:SetPoint("TOPLEFT", todoContainer, "TOPLEFT", 0, yOffset)
            todoFrame:SetPoint("RIGHT", todoContainer, "RIGHT", 0, 0)
            todoFrame:SetHeight(25)

            local checkbox = CreateFrame("CheckButton", nil, todoFrame, "UICheckButtonTemplate")
            checkbox:SetSize(20, 20)
            checkbox:SetPoint("LEFT", todoFrame, "LEFT", 5, 0)
            checkbox:SetChecked(todo.done)
            checkbox:SetScript("OnClick", function(self)
                local d = ns.Zones:GetZone(selectedZone)
                if d and d.todos and d.todos[i] then
                    d.todos[i].done = self:GetChecked()
                    ns.Zones:SaveZone(selectedZone, d)
                    parent.RefreshZoneTodos()
                end
            end)

            local todoEditBox = CreateFrame("EditBox", nil, todoFrame, "InputBoxTemplate")
            todoEditBox:SetPoint("LEFT", checkbox, "RIGHT", 10, 0)
            todoEditBox:SetPoint("RIGHT", todoFrame, "RIGHT", -35, 0)
            todoEditBox:SetHeight(20)
            todoEditBox:SetAutoFocus(false)
            todoEditBox:SetText(todo.text or "")
            if todo.done then
                todoEditBox:SetTextColor(0.5, 0.5, 0.5)
            else
                todoEditBox:SetTextColor(1, 1, 1)
            end
            todoEditBox:SetScript("OnEnterPressed", function(self)
                local d = ns.Zones:GetZone(selectedZone)
                if d and d.todos and d.todos[i] then
                    d.todos[i].text = self:GetText()
                    ns.Zones:SaveZone(selectedZone, d)
                    parent.RefreshZoneTodos()
                end
                self:ClearFocus()
            end)
            todoEditBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
            todoEditBox:SetScript("OnEditFocusLost", function(self)
                local d = ns.Zones:GetZone(selectedZone)
                if d and d.todos and d.todos[i] then
                    d.todos[i].text = self:GetText()
                    ns.Zones:SaveZone(selectedZone, d)
                end
            end)
            todoEditBox:SetScript("OnMouseUp", function(self, button)
                if button == "RightButton" and ns.NotesContextMenu then
                    ns.NotesContextMenu:ShowEditBoxContextMenu(self)
                end
            end)
            if ns.NotesHyperlinks then
                ns.NotesHyperlinks:EnhanceEditBox(todoEditBox)
            end

            local deleteTodoBtn = CreateFrame("Button", nil, todoFrame)
            deleteTodoBtn:SetSize(16, 16)
            deleteTodoBtn:SetPoint("RIGHT", todoFrame, "RIGHT", -5, 0)
            deleteTodoBtn:SetNormalTexture(MEDIA .. "icon-minus.png")
            deleteTodoBtn:SetPushedTexture(MEDIA .. "icon-minus.png")
            deleteTodoBtn:SetHighlightTexture(MEDIA .. "icon-minus.png")
            deleteTodoBtn:GetHighlightTexture():SetAlpha(0.5)
            deleteTodoBtn:SetScript("OnClick", function()
                local d = ns.Zones:GetZone(selectedZone)
                if d and d.todos then
                    table.remove(d.todos, i)
                    ns.Zones:SaveZone(selectedZone, d)
                    parent.RefreshZoneTodos()
                end
            end)

            yOffset = yOffset - 30
        end

        todoContainer:SetHeight(math.abs(yOffset) + 50)
    end

    parent.RefreshZonesList = function()
        for _, item in ipairs(zoneListItems) do
            item:Hide()
        end
        wipe(zoneListItems)

        local regions = { scrollChild:GetRegions() }
        for _, r in ipairs(regions) do r:Hide() end
        local children = { scrollChild:GetChildren() }
        for _, c in ipairs(children) do c:Hide() end

        if not ns.Zones then return end

        RefreshCatOpts()

        local allZones = ns.Zones:GetAllZones()
        local filtered = {}
        for name, data in pairs(allZones) do
            local passCategory = (categoryFilter == "All") or (data.category == categoryFilter)
            local passStorage  = (storageFilter == "All") or (data.storage == storageFilter)
            local passSearch   = (searchFilter == "") or name:lower():find(searchFilter:lower(), 1, true)
            if passCategory and passStorage and passSearch then
                filtered[#filtered + 1] = { name = name, data = data }
            end
        end

        local newZones  = {}
        local favorites = {}
        local regular   = {}
        for _, zone in ipairs(filtered) do
            if zone.data.isNew then
                newZones[#newZones + 1] = zone
            elseif zone.data.favorite then
                favorites[#favorites + 1] = zone
            else
                regular[#regular + 1] = zone
            end
        end

        local function sortZones(a, b)
            local nameA = a.name or ""
            local nameB = b.name or ""
            if currentSort.by == "category" then
                local ca = a.data.category or ""
                local cb = b.data.category or ""
                if ca == cb then return nameA < nameB end
                if currentSort.ascending then return ca < cb else return ca > cb end
            elseif currentSort.by == "color" then
                local ca = a.data.pinColor or ""
                local cb = b.data.pinColor or ""
                if ca == cb then return nameA < nameB end
                if currentSort.ascending then return ca < cb else return ca > cb end
            elseif currentSort.by == "modified" then
                if currentSort.ascending then return (a.data.modified or 0) < (b.data.modified or 0)
                else return (a.data.modified or 0) > (b.data.modified or 0) end
            elseif currentSort.by == "manual" then
                local sa = a.data.sortOrder or 0
                local sb = b.data.sortOrder or 0
                if sa == sb then return nameA < nameB end
                if currentSort.ascending then return sa < sb else return sa > sb end
            else
                if currentSort.ascending then return nameA < nameB else return nameA > nameB end
            end
        end
        table.sort(newZones,  sortZones)
        table.sort(favorites, sortZones)
        table.sort(regular,   sortZones)

        local function CreateSectionHeader(title, yOfs)
            local section = OneWoW_GUI:CreateSectionHeader(scrollChild, { title = title, yOffset = yOfs })
            table.insert(zoneListItems, section)
            return section
        end

        local function BuildZoneRow(zone, yOfs, groupArray, groupIndex)
            local listItemColor = {OneWoW_GUI:GetThemeColor("BG_SECONDARY")}
            local resolvedColor = ns.Config:GetResolvedColorConfig(zone.data.pinColor)
            local cR, cG, cB = resolvedColor.background[1], resolvedColor.background[2], resolvedColor.background[3]

            local row = CreateFrame("Frame", nil, scrollChild, "BackdropTemplate")
            row:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, yOfs)
            row:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", 0, yOfs)
            row:SetHeight(50)
            row:SetBackdrop(BACKDROP_INNER_NO_INSETS)
            row:SetBackdropColor(listItemColor[1], listItemColor[2], listItemColor[3], listItemColor[4])
            row:SetBackdropBorderColor(OneWoW_GUI:GetThemeColor("BORDER_SUBTLE"))

            local colorStrip = row:CreateTexture(nil, "ARTWORK")
            colorStrip:SetSize(4, 46)
            colorStrip:SetPoint("LEFT", row, "LEFT", 2, 0)
            colorStrip:SetColorTexture(cR, cG, cB, 1)

            local titleFS = OneWoW_GUI:CreateFS(row, 12)
            titleFS:SetPoint("TOPLEFT", row, "TOPLEFT", 12, -6)
            titleFS:SetPoint("TOPRIGHT", row, "TOPRIGHT", -80, -6)
            titleFS:SetJustifyH("LEFT")
            titleFS:SetText(zone.name)
            titleFS:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_PRIMARY"))

            local storageFS = OneWoW_GUI:CreateFS(row, 10)
            storageFS:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 12, 6)
            local stText = zone.data.storage == "character" and (L["STORAGE_TYPE_CHARACTER"] or "Char") or (L["STORAGE_ACCOUNT_WIDE"] or "Acct")
            storageFS:SetText(stText)
            storageFS:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_MUTED"))

            local alertBtn = CreateFrame("Button", nil, row)
            alertBtn:SetSize(18, 18)
            alertBtn:SetPoint("TOPRIGHT", row, "TOPRIGHT", -28, -6)
            local aN = alertBtn:CreateTexture(nil, "BACKGROUND")
            aN:SetAllPoints()
            aN:SetTexture(MEDIA .. "icon-alert.png")
            local alertOn = zone.data.alertEnabled ~= false
            aN:SetDesaturated(not alertOn)
            aN:SetAlpha(alertOn and 1.0 or 0.3)
            alertBtn:SetNormalTexture(aN)
            alertBtn:SetScript("OnClick", function()
                if ns.Zones then
                    local zoneData = ns.Zones:GetZone(zone.name)
                    if zoneData then
                        local wasEnabled = zoneData.alertEnabled ~= false
                        zoneData.alertEnabled = not wasEnabled
                        aN:SetDesaturated(wasEnabled)
                        aN:SetAlpha(wasEnabled and 0.3 or 1.0)
                        ns.Zones:SaveZone(zone.name, zoneData)
                    end
                end
            end)
            alertBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["TOOLTIP_ZONE_ALERT"] or "Zone Alert", 1, 1, 1)
                GameTooltip:AddLine(L["TOOLTIP_ZONE_ALERT_DESC"] or "Toggle zone entry alert.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            alertBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

            local pinListBtn = CreateFrame("Button", nil, row)
            pinListBtn:SetSize(18, 18)
            pinListBtn:SetPoint("RIGHT", alertBtn, "LEFT", -2, 0)
            local pN = pinListBtn:CreateTexture(nil, "BACKGROUND")
            pN:SetAllPoints()
            pN:SetTexture(MEDIA .. "icon-pin.png")
            pN:SetDesaturated(not zone.data.pinEnabled)
            pN:SetAlpha(zone.data.pinEnabled and 1.0 or 0.3)
            pinListBtn:SetNormalTexture(pN)
            pinListBtn:SetScript("OnClick", function()
                if ns.Zones and ns.ZonePins then
                    local zoneData = ns.Zones:GetZone(zone.name)
                    if zoneData then
                        if zoneData.pinEnabled then
                            ns.ZonePins:HideZonePin(zone.name)
                            zoneData.pinEnabled = false
                        else
                            zoneData.pinEnabled = true
                            ns.ZonePins:ShowZonePin(zone.name, zoneData)
                        end
                        pN:SetDesaturated(not zoneData.pinEnabled)
                        pN:SetAlpha(zoneData.pinEnabled and 1.0 or 0.3)
                        ns.Zones:SaveZone(zone.name, zoneData)
                        if selectedZone == zone.name then
                            ShowEditor()
                        end
                    end
                end
            end)
            pinListBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["TOOLTIP_NOTE_PIN"] or "Pin", 1, 1, 1)
                GameTooltip:AddLine(L["TOOLTIP_NOTE_PIN_DESC"] or "Pin this zone.", 0.8, 0.8, 0.8, true)
                GameTooltip:Show()
            end)
            pinListBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

            local favBtn = CreateFrame("Button", nil, row)
            favBtn:SetSize(18, 18)
            favBtn:SetPoint("RIGHT", pinListBtn, "LEFT", -2, 0)
            local fN2 = favBtn:CreateTexture(nil, "BACKGROUND")
            fN2:SetAllPoints()
            fN2:SetTexture(MEDIA .. "icon-fav.png")
            fN2:SetDesaturated(not zone.data.favorite)
            fN2:SetAlpha(zone.data.favorite and 1.0 or 0.3)
            favBtn:SetNormalTexture(fN2)
            favBtn:SetScript("OnClick", function()
                if ns.Zones then
                    local zoneData = ns.Zones:GetZone(zone.name)
                    if zoneData then
                        zoneData.favorite = not zoneData.favorite
                        fN2:SetDesaturated(not zoneData.favorite)
                        fN2:SetAlpha(zoneData.favorite and 1.0 or 0.3)
                        ns.Zones:SaveZone(zone.name, zoneData)
                        parent.RefreshZonesList()
                    end
                end
            end)

            local canMoveUp   = groupArray ~= nil and groupIndex ~= nil and groupIndex > 1
            local canMoveDown = groupArray ~= nil and groupIndex ~= nil and groupIndex < #groupArray

            local upBtn = CreateFrame("Button", nil, row)
            upBtn:SetSize(18, 22)
            upBtn:SetPoint("TOPRIGHT", row, "TOPRIGHT", -4, -3)
            upBtn:SetNormalAtlas("common-button-collapseExpand-up")
            upBtn:SetHighlightAtlas("common-button-collapseExpand-up")
            if upBtn:GetNormalTexture()    then upBtn:GetNormalTexture():SetVertexColor(1, 0.82, 0, 1) end
            if upBtn:GetHighlightTexture() then upBtn:GetHighlightTexture():SetVertexColor(1, 1, 0, 0.7) end
            if canMoveUp then upBtn:Show() else upBtn:Hide() end
            upBtn:SetScript("OnClick", function()
                if not canMoveUp then return end
                if currentSort.by ~= "manual" then
                    currentSort.by = "manual"
                    currentSort.ascending = true
                    zoneSortHandle:SetSort("manual", true)
                    OneWoW_Notes.db.global.tabSortPrefs.zones = { by = "manual", ascending = true }
                end
                for i, item in ipairs(groupArray) do item.data.sortOrder = i end
                groupArray[groupIndex].data.sortOrder     = groupIndex - 1
                groupArray[groupIndex - 1].data.sortOrder = groupIndex
                parent.RefreshZonesList()
            end)

            local downBtn = CreateFrame("Button", nil, row)
            downBtn:SetSize(18, 22)
            downBtn:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -4, 3)
            downBtn:SetNormalAtlas("common-button-collapseExpand-down")
            downBtn:SetHighlightAtlas("common-button-collapseExpand-down")
            if downBtn:GetNormalTexture()    then downBtn:GetNormalTexture():SetVertexColor(1, 0.82, 0, 1) end
            if downBtn:GetHighlightTexture() then downBtn:GetHighlightTexture():SetVertexColor(1, 1, 0, 0.7) end
            if canMoveDown then downBtn:Show() else downBtn:Hide() end
            downBtn:SetScript("OnClick", function()
                if not canMoveDown then return end
                if currentSort.by ~= "manual" then
                    currentSort.by = "manual"
                    currentSort.ascending = true
                    zoneSortHandle:SetSort("manual", true)
                    OneWoW_Notes.db.global.tabSortPrefs.zones = { by = "manual", ascending = true }
                end
                for i, item in ipairs(groupArray) do item.data.sortOrder = i end
                groupArray[groupIndex].data.sortOrder     = groupIndex + 1
                groupArray[groupIndex + 1].data.sortOrder = groupIndex
                parent.RefreshZonesList()
            end)

            row:EnableMouse(true)
            row:SetScript("OnMouseDown", function()
                if zone.data.isNew then
                    zone.data.isNew = false
                    zone.data.newTimestamp = nil
                    if ns.Zones then ns.Zones:SaveZone(zone.name, zone.data) end
                end
                selectedZone = zone.name
                ShowEditor()
                parent.RefreshZonesList()
            end)
            row:SetScript("OnEnter", function(self)
                if selectedZone ~= zone.name then
                    self:SetBackdropColor(listItemColor[1] * 1.2, listItemColor[2] * 1.2, listItemColor[3] * 1.2, listItemColor[4] + 0.1)
                end
            end)
            row:SetScript("OnLeave", function(self)
                if selectedZone ~= zone.name then
                    self:SetBackdropColor(listItemColor[1], listItemColor[2], listItemColor[3], listItemColor[4])
                end
            end)

            if selectedZone == zone.name then
                row:SetBackdropColor(listItemColor[1] + 0.15, listItemColor[2] + 0.15, listItemColor[3] + 0.15, 0.9)
                row:SetBackdropBorderColor(1, 0.82, 0, 1)
            end

            table.insert(zoneListItems, row)
        end

        local yOffset = 0

        if #newZones > 0 then
            CreateSectionHeader(L["NOTES_SECTION_NEW"] or "New", yOffset)
            yOffset = yOffset - 30
        end
        for i, zone in ipairs(newZones) do BuildZoneRow(zone, yOffset, newZones, i) yOffset = yOffset - 55 end

        if #favorites > 0 then
            CreateSectionHeader(L["NOTES_SECTION_FAVORITES"] or "Favorites", yOffset)
            yOffset = yOffset - 30
        end
        for i, zone in ipairs(favorites) do BuildZoneRow(zone, yOffset, favorites, i) yOffset = yOffset - 55 end

        if #regular > 0 then
            CreateSectionHeader(L["TAB_ZONES"], yOffset)
            yOffset = yOffset - 30
        end
        for i, zone in ipairs(regular) do BuildZoneRow(zone, yOffset, regular, i) yOffset = yOffset - 55 end

        scrollChild:SetHeight(math.abs(yOffset) + 50)
        if leftStatusText then
            leftStatusText:SetText(string.format(L["UI_COUNT_FORMAT"], L["TAB_ZONES"], #newZones + #favorites + #regular))
        end
    end

    parent.RefreshZonesList()
end

local function MakeZoneLabel(parent, text, x, y)
    local lbl = OneWoW_GUI:CreateFS(parent, 12)
    lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    lbl:SetText(text)
    lbl:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_SECONDARY"))
    return lbl
end

local function MakeZoneInput(parent, x, y, w)
    local box = OneWoW_GUI:CreateEditBox(parent, {
        width = w,
        height = 26,
        placeholderText = "",
    })
    box:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    box:SetText("")
    box:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_PRIMARY"))
    box:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
    box:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    box:SetScript("OnEditFocusGained", function(self)
        self:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_PRIMARY"))
    end)
    box:SetScript("OnEditFocusLost", function(self)
        if self:GetText() == "" then
            self:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_MUTED"))
        end
    end)
    return box
end

local function MakeZoneSlider(parent, _, x, y, w, minV, maxV, defV, fmt)
    local step = (fmt == "pct") and 0.05 or 1
    local fmtStr = (fmt == "pct") and "%d%%" or "%d"
    local container = OneWoW_GUI:CreateSlider(parent, {
        minVal = minV,
        maxVal = maxV,
        step = step,
        currentVal = defV,
        onChange = function() end,
        width = w,
        fmt = fmtStr,
    })
    container:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    return container, nil, container
end

function ns.UI.ShowManualZoneEntryDialog(refreshParent)
    local COL1_X = 10
    local COL2_X = 300
    local COL_W  = 260
    local ROW_H   = 50
    local LBL_GAP = 18

    local dialog = ns.UI.CreateThemedDialog({
        name            = "OneWoW_NotesManualZoneEntry",
        title           = L["ZONE_MANUAL_ENTRY_TITLE"] or "Add Zone",
        width           = 580,
        height          = 610,
        destroyOnClose  = true,
        buttons = {
            {
                text = L["BUTTON_ADD_NOTE"] or "Add",
                onClick = function(dlg)
                    local name = dlg._nameInput and dlg._nameInput:GetText() or ""
                    if name == "" then
                        print("|cFFFFD100OneWoW - Zones:|r " .. (L["ZONE_ERROR_NAME_REQUIRED"] or "Zone name is required."))
                        return
                    end

                    if ns.Zones and ns.Zones:GetZone(name) then
                        print("|cFFFFD100OneWoW - Zones:|r " .. string.format(L["MSG_ZONE_EXISTS"] or "Zone exists: %s", name))
                        return
                    end

                    local cat        = dlg._catDD      and dlg._catDD:GetValue()      or "General"
                    local store      = dlg._storeDD    and dlg._storeDD:GetValue()    or "account"
                    local pinColor   = dlg._colorDD    and dlg._colorDD:GetValue()    or "hunter"
                    local fontCol    = dlg._fontColDD  and dlg._fontColDD:GetValue()  or "match"
                    local fontFamily = dlg._fontFamily or nil
                    local fontSize   = dlg._fontSize   or 12
                    local opacity    = dlg._opacity    or 0.9

                    local noteContent = dlg._noteEditBox and dlg._noteEditBox:GetText() or ""

                    local mapID = dlg._validatedMapID
                    if ns.Zones then
                        ns.Zones:AddZone(name, {
                            content = noteContent, category = cat, storage = store,
                            pinColor = pinColor, fontColor = fontCol,
                            fontFamily = fontFamily,
                            fontSize = fontSize, opacity = opacity,
                            mapID = mapID,
                        })
                        print("|cFFFFD100OneWoW - Zones:|r " .. string.format(L["MSG_ZONE_ADDED"] or "Added: %s", name))
                        dlg:Hide()
                        if refreshParent and refreshParent.RefreshZonesList then refreshParent.RefreshZonesList() end
                        if refreshParent and refreshParent.SelectZone then refreshParent.SelectZone(name) end
                    end
                end,
            },
            { text = L["BUTTON_CANCEL"], onClick = function(dlg) dlg:Hide() end },
        },
    })

    if dialog.built then dialog:Show() return end
    dialog.built = true

    local content = dialog.content
    local yPos = -10

    MakeZoneLabel(content, L["LABEL_ZONE_NAME"] or "Zone Name:", COL1_X, yPos)
    dialog._nameInput = MakeZoneInput(content, COL1_X, yPos - LBL_GAP, COL_W * 2 + (COL2_X - COL1_X - COL_W))
    dialog._nameInput:SetAutoFocus(true)
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_MAP_ID_OPTIONAL"] or "Map ID (optional):", COL1_X, yPos)
    local mapIDInput = MakeZoneInput(content, COL1_X, yPos - LBL_GAP, 120)
    mapIDInput:SetNumeric(true)
    dialog._validatedMapID = nil

    local validateBtn = OneWoW_GUI:CreateFitTextButton(content, { text = L["BUTTON_VALIDATE"] or "Validate", height = 26, minWidth = 70 })
    validateBtn:SetPoint("LEFT", mapIDInput, "RIGHT", 6, 0)

    local validationFS = OneWoW_GUI:CreateFS(content, 10)
    validationFS:SetPoint("LEFT", validateBtn, "RIGHT", 8, 0)
    validationFS:SetText(L["ZONE_VALIDATE_HINT"] or "Enter ID & click Validate")
    validationFS:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_MUTED"))

    validateBtn:SetScript("OnClick", function()
        local mapID = tonumber(mapIDInput:GetText())
        if not mapID or mapID <= 0 then
            validationFS:SetText(L["ZONE_INVALID_MAP_ID"] or "Enter a valid number.")
            validationFS:SetTextColor(0.8, 0.2, 0.2, 1)
            dialog._validatedMapID = nil
            return
        end
        local mapInfo = C_Map.GetMapInfo(mapID)
        if mapInfo and mapInfo.name then
            validationFS:SetText(mapInfo.name)
            validationFS:SetTextColor(0.2, 1.0, 0.2, 1)
            dialog._nameInput:SetText(mapInfo.name)
            dialog._validatedMapID = mapID
        else
            validationFS:SetText(L["ZONE_MAP_NOT_FOUND"] or "Map ID not found.")
            validationFS:SetTextColor(0.8, 0.2, 0.2, 1)
            dialog._validatedMapID = nil
        end
    end)
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_CATEGORY"], COL1_X, yPos)
    local catDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    catDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL1_X, yPos - LBL_GAP)
    local catOpts = {}
    if ns.Zones then
        for _, c in ipairs(ns.Zones:GetCategories()) do
            catOpts[#catOpts + 1] = {text = c, value = c}
        end
    end
    catDD:SetOptions(catOpts)
    catDD:SetSelected("General")
    dialog._catDD = catDD

    MakeZoneLabel(content, L["LABEL_STORAGE"], COL2_X, yPos)
    local storeDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    storeDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    storeDD:SetOptions({
        {text = L["STORAGE_ACCOUNT_WIDE"],   value = "account"},
        {text = L["STORAGE_TYPE_CHARACTER"], value = "character"},
    })
    storeDD:SetSelected("account")
    dialog._storeDD = storeDD
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_NOTE_COLOR"], COL1_X, yPos)
    local colorDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    colorDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL1_X, yPos - LBL_GAP)
    local colorOpts = {}
    for key, colorData in pairs(ns.Config.PIN_COLORS) do
        colorOpts[#colorOpts + 1] = {text = colorData.name, value = key}
    end
    table.sort(colorOpts, function(a, b) return a.text < b.text end)
    colorDD:SetOptions(colorOpts)
    colorDD:SetSelected("hunter")
    dialog._colorDD = colorDD

    MakeZoneLabel(content, L["LABEL_FONT_COLOR"], COL2_X, yPos)
    local fontColDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    fontColDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    fontColDD:SetOptions({
        {text = "OneWoW Sync",                value = "sync"},
        {text = L["FONT_COLOR_MATCHING"],    value = "match"},
        {text = L["FONT_COLOR_WHITE"],      value = "white"},
        {text = L["FONT_COLOR_BLACK"],      value = "black"},
    })
    fontColDD:SetSelected("match")
    dialog._fontColDD = fontColDD
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_FONT_SIZE"], COL1_X, yPos)
    dialog._fontSize = 12
    local fontSizeSlider, fontSizeTxt, fontSizeContainer = MakeZoneSlider(content, "OneWoW_ZoneAddFontSize", COL1_X, yPos - LBL_GAP, COL_W, 10, 20, 12, "int")
    if fontSizeContainer then
        local sliderChild = select(1, fontSizeContainer:GetChildren())
        if sliderChild then
            sliderChild:SetScript("OnValueChanged", function(_, value)
                local val = math.floor(value + 0.5)
                dialog._fontSize = val
            end)
        end
    elseif fontSizeSlider.SetScript then
        fontSizeSlider:SetScript("OnValueChanged", function(_, value)
            local val = math.floor(value + 0.5)
            if fontSizeTxt then fontSizeTxt:SetText(tostring(val)) end
            dialog._fontSize = val
        end)
    end

    MakeZoneLabel(content, L["LABEL_NOTE_FONT"], COL2_X, yPos)
    dialog._fontFamily = nil
    local addPreviewEditBox = nil
    local addFontOpts = ns.Config:GetFontOptions()
    local addFontDD = ns.UI.CreateFontDropdown(content, COL_W, 26)
    addFontDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    addFontDD:SetOptions(addFontOpts)
    addFontDD:SetSelected("default")
    addFontDD.onSelect = function(value)
        local fontValue = (value == "default") and nil or value
        dialog._fontFamily = fontValue
        if addPreviewEditBox then
            local fp = ns.Config:ResolveFontPath(fontValue)
            addPreviewEditBox:SetFont(fp, dialog._fontSize or 12, dialog._fontOutline or "")
        end
    end
    dialog._fontFamilyDD = addFontDD
    yPos = yPos - ROW_H

    MakeZoneLabel(content, "Font Outline", COL2_X, yPos)
    dialog._fontOutline = ""
    local addOutlineDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    addOutlineDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    addOutlineDD:SetOptions({
        {text = "None", value = ""},
        {text = "Outline", value = "OUTLINE"},
        {text = "Thick Outline", value = "THICKOUTLINE"},
    })
    addOutlineDD:SetSelected("")
    addOutlineDD.onSelect = function(value)
        dialog._fontOutline = value
        if addPreviewEditBox then
            local fp = ns.Config:ResolveFontPath(dialog._fontFamily)
            addPreviewEditBox:SetFont(fp, dialog._fontSize or 12, value)
        end
    end
    dialog._outlineDD = addOutlineDD

    MakeZoneLabel(content, L["LABEL_OPACITY"], COL1_X, yPos)
    dialog._opacity = 0.9
    local opacitySlider, opacityTxt, opacityContainer = MakeZoneSlider(content, "OneWoW_ZoneAddOpacity", COL1_X, yPos - LBL_GAP, COL_W, 0.5, 1.0, 0.9, "pct")
    if opacityContainer then
        local sliderChild = select(1, opacityContainer:GetChildren())
        if sliderChild then
            sliderChild:SetScript("OnValueChanged", function(_, value)
                dialog._opacity = value
            end)
        end
    elseif opacitySlider.SetScript then
        opacitySlider:SetScript("OnValueChanged", function(_, value)
            local val = math.floor(value * 100 + 0.5)
            if opacityTxt then opacityTxt:SetText(val .. "%") end
            dialog._opacity = value
        end)
    end
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_NOTE_CONTENT"] or "Note:", COL1_X, yPos)
    yPos = yPos - LBL_GAP

    local noteBg = CreateFrame("Frame", nil, content, "BackdropTemplate")
    noteBg:SetPoint("TOPLEFT",     content, "TOPLEFT",     COL1_X, yPos)
    noteBg:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -COL1_X, 6)
    noteBg:SetBackdrop(BACKDROP_INNER_NO_INSETS)
    noteBg:SetBackdropColor(OneWoW_GUI:GetThemeColor("BG_SECONDARY"))
    noteBg:SetBackdropBorderColor(OneWoW_GUI:GetThemeColor("BORDER_DEFAULT"))

    local noteScroll = OneWoW_GUI:CreateScrollFrame(noteBg, {})
    noteScroll:SetPoint("TOPLEFT",     noteBg, "TOPLEFT",     4, -4)
    noteScroll:SetPoint("BOTTOMRIGHT", noteBg, "BOTTOMRIGHT", -26, 4)

    local noteEditBox = CreateFrame("EditBox", nil, noteScroll)
    noteEditBox:SetMultiLine(true)
    noteEditBox:SetFont(ns.Config:ResolveFontPath(dialog._fontFamily), dialog._fontSize or 12, dialog._fontOutline or "")
    noteEditBox:SetAutoFocus(false)
    noteEditBox:SetMaxLetters(0)
    noteScroll:SetScrollChild(noteEditBox)
    noteScroll:HookScript("OnSizeChanged", function(_, w)
        noteEditBox:SetWidth(math.max(1, w))
    end)
    noteEditBox._skipGlobalFont = true
    addPreviewEditBox = noteEditBox
    dialog._noteEditBox = noteEditBox

    dialog:Show()
end

function ns.UI.ShowZonePropertiesDialog(zoneName, refreshParent)
    if not zoneName or not ns.Zones then return end
    local zoneData = ns.Zones:GetZone(zoneName)
    if not zoneData then return end

    local COL1_X  = 10
    local COL2_X  = 300
    local COL_W   = 260
    local ROW_H   = 50
    local LBL_GAP = 18

    local dialog = ns.UI.CreateThemedDialog({
        name            = "OneWoW_NotesZoneProperties",
        title           = (L["DIALOG_ZONE_PROPERTIES"] or "Zone Properties") .. ": " .. zoneName,
        width           = 580,
        height          = 600,
        destroyOnClose  = true,
        buttons = {
            { text = L["BUTTON_CLOSE"], onClick = function(dlg) dlg:Hide() end },
        },
    })

    if dialog.built then dialog:Show() return end
    dialog.built = true

    local content = dialog.content
    local yPos = -10

    local function SaveField(field, value)
        local d = ns.Zones:GetZone(zoneName)
        if d then
            d[field] = value
            ns.Zones:SaveZone(zoneName, d)
        end
        if refreshParent and refreshParent.RefreshZonesList then refreshParent.RefreshZonesList() end
    end

    local function RefreshEditor()
        if refreshParent and refreshParent.SelectZone then
            refreshParent.SelectZone(zoneName)
        end
        if ns.ZonePins and ns.ZonePins.RefreshZonePinColors then
            ns.ZonePins:RefreshZonePinColors(zoneName)
        end
    end

    MakeZoneLabel(content, L["LABEL_ZONE_NAME"] or "Zone Name:", COL1_X, yPos)
    local nameInput = MakeZoneInput(content, COL1_X, yPos - LBL_GAP, COL_W * 2 + (COL2_X - COL1_X - COL_W))
    nameInput:SetText(zoneName)
    nameInput:SetScript("OnEnterPressed", function(self)
        local newName = self:GetText()
        if newName ~= "" and newName ~= zoneName then
            local d = ns.Zones:GetZone(zoneName)
            if d then
                ns.Zones:RemoveZone(zoneName)
                ns.Zones:AddZone(newName, d)
                zoneName = newName
                if refreshParent and refreshParent.RefreshZonesList then refreshParent.RefreshZonesList() end
                if refreshParent and refreshParent.SelectZone then refreshParent.SelectZone(newName) end
            end
        end
        self:ClearFocus()
    end)
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_MAP_ID_OPTIONAL"] or "Map ID:", COL1_X, yPos)
    local mapIDInput = MakeZoneInput(content, COL1_X, yPos - LBL_GAP, 120)
    mapIDInput:SetNumeric(true)
    mapIDInput:SetText(zoneData.mapID and tostring(zoneData.mapID) or "")

    local validateBtn = OneWoW_GUI:CreateFitTextButton(content, { text = L["BUTTON_VALIDATE"] or "Validate", height = 26, minWidth = 70 })
    validateBtn:SetPoint("LEFT", mapIDInput, "RIGHT", 6, 0)

    local validationFS = OneWoW_GUI:CreateFS(content, 10)
    validationFS:SetPoint("LEFT", validateBtn, "RIGHT", 8, 0)
    if zoneData.mapID then
        local mapInfo = C_Map.GetMapInfo(zoneData.mapID)
        if mapInfo then
            validationFS:SetText(mapInfo.name)
            validationFS:SetTextColor(0.2, 1.0, 0.2, 1)
        else
            validationFS:SetText(L["ZONE_MAP_NOT_FOUND"] or "Map ID not found.")
            validationFS:SetTextColor(0.8, 0.2, 0.2, 1)
        end
    else
        validationFS:SetText(L["ZONE_VALIDATE_HINT"] or "Enter ID & click Validate")
        validationFS:SetTextColor(OneWoW_GUI:GetThemeColor("TEXT_MUTED"))
    end

    validateBtn:SetScript("OnClick", function()
        local mapID = tonumber(mapIDInput:GetText())
        if not mapID or mapID <= 0 then
            validationFS:SetText(L["ZONE_INVALID_MAP_ID"] or "Enter a valid number.")
            validationFS:SetTextColor(0.8, 0.2, 0.2, 1)
            return
        end
        local mapInfo = C_Map.GetMapInfo(mapID)
        if mapInfo and mapInfo.name then
            validationFS:SetText(mapInfo.name)
            validationFS:SetTextColor(0.2, 1.0, 0.2, 1)
            SaveField("mapID", mapID)
            nameInput:SetText(mapInfo.name)
            local d = ns.Zones:GetZone(zoneName)
            if d then
                ns.Zones:RemoveZone(zoneName)
                ns.Zones:AddZone(mapInfo.name, d)
                zoneName = mapInfo.name
                if refreshParent and refreshParent.RefreshZonesList then refreshParent.RefreshZonesList() end
                RefreshEditor()
            end
        else
            validationFS:SetText(L["ZONE_MAP_NOT_FOUND"] or "Map ID not found.")
            validationFS:SetTextColor(0.8, 0.2, 0.2, 1)
        end
    end)
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_CATEGORY"], COL1_X, yPos)
    local catDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    catDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL1_X, yPos - LBL_GAP)
    local catOpts = {}
    if ns.Zones then
        for _, c in ipairs(ns.Zones:GetCategories()) do
            catOpts[#catOpts + 1] = {text = c, value = c}
        end
    end
    catDD:SetOptions(catOpts)
    catDD:SetSelected(zoneData.category or "General")
    catDD.onSelect = function(value) SaveField("category", value) end

    MakeZoneLabel(content, L["LABEL_STORAGE"], COL2_X, yPos)
    local storeDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    storeDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    storeDD:SetOptions({
        {text = L["STORAGE_ACCOUNT_WIDE"],   value = "account"},
        {text = L["STORAGE_TYPE_CHARACTER"], value = "character"},
    })
    storeDD:SetSelected(zoneData.storage or "account")
    storeDD.onSelect = function(value)
        local d = ns.Zones:GetZone(zoneName)
        if d then d.storage = value ns.Zones:SaveZone(zoneName, d) end
        if refreshParent and refreshParent.RefreshZonesList then refreshParent.RefreshZonesList() end
    end
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_NOTE_COLOR"], COL1_X, yPos)
    local colorDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    colorDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL1_X, yPos - LBL_GAP)
    local colorOpts = {}
    for key, colorData in pairs(ns.Config.PIN_COLORS) do
        colorOpts[#colorOpts + 1] = {text = colorData.name, value = key}
    end
    table.sort(colorOpts, function(a, b) return a.text < b.text end)
    colorDD:SetOptions(colorOpts)
    colorDD:SetSelected(zoneData.pinColor or "hunter")
    colorDD.onSelect = function(value)
        SaveField("pinColor", value)
        RefreshEditor()
    end

    MakeZoneLabel(content, L["LABEL_FONT_COLOR"], COL2_X, yPos)
    local fontColorDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    fontColorDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    fontColorDD:SetOptions({
        {text = "OneWoW Sync",                value = "sync"},
        {text = L["FONT_COLOR_MATCHING"],    value = "match"},
        {text = L["FONT_COLOR_WHITE"],      value = "white"},
        {text = L["FONT_COLOR_BLACK"],      value = "black"},
    })
    fontColorDD:SetSelected(zoneData.fontColor or "match")
    fontColorDD.onSelect = function(value)
        SaveField("fontColor", value)
        RefreshEditor()
    end
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_FONT_SIZE"], COL1_X, yPos)
    local propFontSizeSlider, propFontSizeTxt, propFontSizeContainer = MakeZoneSlider(content, "OneWoW_ZonePropFontSize", COL1_X, yPos - LBL_GAP, COL_W, 10, 20, zoneData.fontSize or 12, "int")
    if propFontSizeContainer then
        local sliderChild = select(1, propFontSizeContainer:GetChildren())
        if sliderChild then
            sliderChild:SetScript("OnValueChanged", function(_, value)
                local val = math.floor(value + 0.5)
                SaveField("fontSize", val)
                RefreshEditor()
            end)
        end
    elseif propFontSizeSlider.SetScript then
        propFontSizeSlider:SetScript("OnValueChanged", function(_, value)
            local val = math.floor(value + 0.5)
            if propFontSizeTxt then propFontSizeTxt:SetText(tostring(val)) end
            SaveField("fontSize", val)
            RefreshEditor()
        end)
    end

    MakeZoneLabel(content, L["LABEL_NOTE_FONT"], COL2_X, yPos)
    local propPreviewEditBox = nil
    local propFontOpts = ns.Config:GetFontOptions()
    local propFontDD = ns.UI.CreateFontDropdown(content, COL_W, 26)
    propFontDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    propFontDD:SetOptions(propFontOpts)
    propFontDD:SetSelected(zoneData.fontFamily or "default")
    propFontDD.onSelect = function(value)
        local fontValue = (value == "default") and nil or value
        SaveField("fontFamily", fontValue)
        RefreshEditor()
        if propPreviewEditBox then
            local fp = ns.Config:ResolveFontPath(fontValue)
            local d = ns.Zones:GetZone(zoneName)
            propPreviewEditBox:SetFont(fp, d and d.fontSize or 12, d and d.fontOutline or "")
        end
    end
    yPos = yPos - ROW_H

    MakeZoneLabel(content, "Font Outline", COL2_X, yPos)
    local propOutlineDD = ns.UI.CreateThemedDropdown(content, "", COL_W, 26)
    propOutlineDD:SetPoint("TOPLEFT", content, "TOPLEFT", COL2_X, yPos - LBL_GAP)
    propOutlineDD:SetOptions({
        {text = "None", value = ""},
        {text = "Outline", value = "OUTLINE"},
        {text = "Thick Outline", value = "THICKOUTLINE"},
    })
    propOutlineDD:SetSelected(zoneData.fontOutline or "")
    propOutlineDD.onSelect = function(value)
        SaveField("fontOutline", value)
        RefreshEditor()
        if propPreviewEditBox then
            local d = ns.Zones:GetZone(zoneName)
            local fp = ns.Config:ResolveFontPath(d and d.fontFamily)
            propPreviewEditBox:SetFont(fp, d and d.fontSize or 12, value)
        end
    end

    MakeZoneLabel(content, L["LABEL_OPACITY"], COL1_X, yPos)
    local propOpacitySlider, propOpacityTxt, propOpacityContainer = MakeZoneSlider(content, "OneWoW_ZonePropOpacity", COL1_X, yPos - LBL_GAP, COL_W, 0.5, 1.0, zoneData.opacity or 0.9, "pct")
    if propOpacityContainer then
        local sliderChild = select(1, propOpacityContainer:GetChildren())
        if sliderChild then
            sliderChild:SetScript("OnValueChanged", function(_, value)
                SaveField("opacity", value)
                RefreshEditor()
            end)
        end
    elseif propOpacitySlider.SetScript then
        propOpacitySlider:SetScript("OnValueChanged", function(_, value)
            local val = math.floor(value * 100 + 0.5)
            if propOpacityTxt then propOpacityTxt:SetText(val .. "%") end
            SaveField("opacity", value)
            RefreshEditor()
        end)
    end
    yPos = yPos - ROW_H

    MakeZoneLabel(content, L["LABEL_NOTE_PREVIEW"] or "Note:", COL1_X, yPos)
    yPos = yPos - LBL_GAP

    local noteBg = CreateFrame("Frame", nil, content, "BackdropTemplate")
    noteBg:SetPoint("TOPLEFT",     content, "TOPLEFT",     COL1_X, yPos)
    noteBg:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -COL1_X, 6)
    noteBg:SetBackdrop(BACKDROP_INNER_NO_INSETS)
    noteBg:SetBackdropColor(OneWoW_GUI:GetThemeColor("BG_SECONDARY"))
    noteBg:SetBackdropBorderColor(OneWoW_GUI:GetThemeColor("BORDER_DEFAULT"))

    local noteScroll = OneWoW_GUI:CreateScrollFrame(noteBg, {})
    noteScroll:SetPoint("TOPLEFT",     noteBg, "TOPLEFT",     4, -4)
    noteScroll:SetPoint("BOTTOMRIGHT", noteBg, "BOTTOMRIGHT", -26, 4)

    local noteEditBox = CreateFrame("EditBox", nil, noteScroll)
    noteEditBox:SetMultiLine(true)
    local propInitFontPath = ns.Config:ResolveFontPath(zoneData.fontFamily)
    noteEditBox:SetFont(propInitFontPath, zoneData.fontSize or 12, zoneData.fontOutline or "")
    noteEditBox:SetAutoFocus(false)
    noteEditBox:SetMaxLetters(0)
    noteEditBox:SetText(zoneData.content or "")
    noteEditBox._skipGlobalFont = true
    propPreviewEditBox = noteEditBox
    noteEditBox:EnableMouse(false)
    noteScroll:SetScrollChild(noteEditBox)
    noteScroll:HookScript("OnSizeChanged", function(_, w)
        noteEditBox:SetWidth(math.max(1, w))
    end)

    dialog:Show()
end
