-- Portal Authority — options/shared_media.lua
-- Shared media, font, sound, color, and low-level settings UI constants.
--
-- Depends on: PortalAuthority
-- Exposes:    PortalAuthority.OptionsInternal media helpers; PortalAuthority:RefreshSettingsWindowHostVisuals
-- DB keys:    dockFont*, summonRequestSound*, summonStoneRequestSound*
-- Events:     none
-- Search key: none

PortalAuthority = PortalAuthority or {}

if PortalAuthority.IsSettingsBaselineGateEnabled and PortalAuthority:IsSettingsBaselineGateEnabled() then
    return
end

if not Settings or not Settings.RegisterCanvasLayoutCategory then
    return
end

PortalAuthority.OptionsInternal = PortalAuthority.OptionsInternal or {}
local OptionsInternal = PortalAuthority.OptionsInternal
local L = PortalAuthority.L

local DEBUG_SETTINGS = OptionsInternal.DEBUG_SETTINGS == true

local function localizedText(key, fallback)
    local value = L and L[key]
    if type(value) == "string" and value ~= "" and value ~= key then
        return value
    end
    return fallback
end

local function debugSettingsLog(...)
    if not DEBUG_SETTINGS then
        return
    end
    local prefix = "[PortalAuthority:Options]"
    if PortalAuthority and PortalAuthority.DebugPrint then
        PortalAuthority:DebugPrint(prefix, ...)
    else
        print(prefix, ...)
    end
end

local function trim(text)
    if type(text) ~= "string" then
        return ""
    end
    local cleaned = text:gsub("^%s+", ""):gsub("%s+$", "")
    return cleaned
end

local function rgba255(r, g, b, a)
    return (tonumber(r) or 255) / 255,
        (tonumber(g) or 255) / 255,
        (tonumber(b) or 255) / 255,
        a == nil and 1 or math.max(0, math.min(1, tonumber(a) or 1))
end

local FLAT_SETTINGS_BACKDROP = {
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
    insets = { left = 0, right = 0, top = 0, bottom = 0 },
}

local SETTINGS_WINDOW_HOSTED_TOP_RHYTHM_NUDGE = 6
local SETTINGS_WINDOW_SCROLLBAR_WIDTH = 8
local SETTINGS_WINDOW_SCROLLBAR_GAP = 10
local SETTINGS_WINDOW_SCROLLBAR_MIN_THUMB_HEIGHT = 24
local SETTINGS_WINDOW_HOST_COLORS = {
    idleBg = { rgba255(26, 28, 36, 0.95) },
    idleBorder = { rgba255(61, 69, 82, 1.0) },
    idleText = { rgba255(200, 200, 210, 1.0) },
    hoverBg = { rgba255(34, 37, 46, 0.97) },
    hoverBorder = { rgba255(88, 98, 112, 1.0) },
    hoverText = { rgba255(222, 224, 230, 1.0) },
    onBg = { rgba255(18, 48, 52, 0.96) },
    onBorder = { rgba255(40, 130, 120, 0.80) },
    onText = { rgba255(170, 240, 230, 1.0) },
    offBg = { rgba255(48, 14, 30, 0.93) },
    offBorder = { rgba255(60, 16, 35, 1.0) },
    offText = { rgba255(220, 185, 200, 1.0) },
    disabledBg = { rgba255(44, 46, 54, 0.92) },
    disabledBorder = { rgba255(86, 92, 102, 0.88) },
    disabledText = { rgba255(150, 154, 164, 0.96) },
    statusGoodBorder = { rgba255(40, 130, 120, 0.90) },
    statusGoodText = { rgba255(170, 240, 230, 1.0) },
}
local SETTINGS_WINDOW_SCROLLBAR_TRACK = { rgba255(10, 10, 18, 1.0) }
local SETTINGS_WINDOW_SCROLLBAR_THUMB = { rgba255(42, 42, 58, 0.5) }
local SETTINGS_WINDOW_SCROLLBAR_THUMB_HOVER = { rgba255(58, 58, 72, 0.6) }

local function unpackColor(color)
    if type(color) ~= "table" then
        return 1, 1, 1, 1
    end
    return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1
end

local function frameIsAlive(frame)
    local helper = OptionsInternal.frameIsAlive
    if type(helper) == "function" then
        return helper(frame)
    end
    return frame ~= nil and type(frame.GetObjectType) == "function"
end

local function isUsingCustomSettingsWindowVisualStyle(frame)
    local helper = OptionsInternal.isUsingCustomSettingsWindowVisualStyle
    if type(helper) == "function" then
        return helper(frame)
    end
    return false
end

local function refreshManagedFontObject(object)
    if not object or object._paGlobalFontManaged ~= true or not object.SetFont then
        return
    end

    local size = tonumber(object._paGlobalFontSize) or select(2, object:GetFont()) or 12
    local flags = object._paGlobalFontFlags
    if flags == nil then
        flags = ""
    end

    if PortalAuthority and type(PortalAuthority.ApplyGlobalFont) == "function" then
        PortalAuthority:ApplyGlobalFont(object, size, flags)
    end
end

local function refreshSettingsWindowVisualTree(object, visited)
    if not frameIsAlive(object) then
        return
    end
    visited = visited or {}
    if visited[object] then
        return
    end
    visited[object] = true

    refreshManagedFontObject(object)
    if type(object._paSettingsWindowVisualRefresh) == "function" then
        pcall(object._paSettingsWindowVisualRefresh, object)
    end

    if object.GetRegions then
        for i = 1, select("#", object:GetRegions()) do
            local region = select(i, object:GetRegions())
            if frameIsAlive(region) then
                refreshSettingsWindowVisualTree(region, visited)
            else
                refreshManagedFontObject(region)
                if region and type(region._paSettingsWindowVisualRefresh) == "function" then
                    pcall(region._paSettingsWindowVisualRefresh, region)
                end
            end
        end
    end
    if object.GetChildren then
        for i = 1, select("#", object:GetChildren()) do
            refreshSettingsWindowVisualTree(select(i, object:GetChildren()), visited)
        end
    end
end

if PortalAuthority then
    function PortalAuthority:RefreshSettingsWindowHostVisuals(root)
        refreshSettingsWindowVisualTree(root, {})
    end
end

local BUILTIN_FONT_CHOICES = {
    { label = localizedText("COMMON_GLOBAL_UI_FONT", "Global UI Font"), value = "" },
    { label = "Friz Quadrata", value = "Fonts\\FRIZQT__.TTF" },
    { label = "Arial Narrow", value = "Fonts\\ARIALN.TTF" },
    { label = "Morpheus", value = "Fonts\\MORPHEUS.ttf" },
    { label = "Skurri", value = "Fonts\\SKURRI.ttf" },
    { label = "2002", value = "Fonts\\2002.ttf" },
    { label = "Nimrod", value = "Fonts\\NIM_____.ttf" },
    { label = "Damage", value = "Fonts\\DAMAGE.ttf" },
}

local SUMMON_SOUND_CHANNEL_CHOICES = {
    { label = localizedText("COMMON_MASTER", "Master"), value = "Master" },
    { label = localizedText("COMMON_SFX", "SFX"), value = "SFX" },
    { label = localizedText("COMMON_MUSIC", "Music"), value = "Music" },
    { label = localizedText("COMMON_AMBIENCE", "Ambience"), value = "Ambience" },
    { label = localizedText("COMMON_DIALOG", "Dialog"), value = "Dialog" },
}

local function getPortalMedia()
    local media = PortalAuthority and PortalAuthority.Media
    if type(media) == "table" then
        return media
    end
    return nil
end

local function normalizeSoundChoiceValue(value)
    local media = getPortalMedia()
    if media and media.NormalizeBundledSoundValue then
        return media.NormalizeBundledSoundValue(value)
    end
    return value
end

local function compareChoiceLabels(left, right)
    return tostring(left.label or ""):lower() < tostring(right.label or ""):lower()
end

local function normalizeChoicePathKey(path)
    if type(path) ~= "string" then
        return nil
    end
    local normalized = trim(path):gsub("/", "\\")
    if normalized == "" then
        return "__GLOBAL_FONT__"
    end
    return normalized:lower()
end

local function resolveFontChoicePath(option)
    if type(option) ~= "table" then
        return nil
    end

    local path = option.path
    if type(path) == "string" then
        path = trim(path):gsub("/", "\\")
        if path ~= "" then
            return path
        end
    end

    local value = option.value
    if value == "" then
        return ""
    end
    if type(value) == "string" then
        local normalized = trim(value):gsub("/", "\\")
        if normalized:find("\\", 1, true) then
            return normalized
        end
    end

    return nil
end

local function getSoundChoiceValueKey(value)
    local normalized = normalizeSoundChoiceValue(value)
    if type(normalized) == "number" then
        return "num:" .. tostring(normalized)
    end
    return "str:" .. tostring(normalized or "")
end

local function buildFallbackSoundChoices(currentValue, opts)
    opts = type(opts) == "table" and opts or {}

    local media = getPortalMedia()
    local choices = {}
    local seen = {}

    local function add(label, value)
        if value == nil then
            value = ""
        end

        local key = getSoundChoiceValueKey(value)
        if seen[key] then
            return
        end

        seen[key] = true
        choices[#choices + 1] = {
            label = tostring(label or value or ""),
            value = value,
        }
    end

    if opts.includeNone then
        add(localizedText("COMMON_NONE", "None"), "")
    end

    if media and type(media.BundledSounds) == "table" then
        for _, entry in ipairs(media.BundledSounds) do
            add(entry.label, entry.value)
        end
    else
        add("Overclock: Pause", "Overclock: Pause")
        add("Overclock: OOT Press Start", "Overclock: OOT Press Start")
        add("Overclock: MGS", "Overclock: MGS")
    end

    local lsm = LibStub and LibStub("LibSharedMedia-3.0", true)
    if lsm and lsm.List then
        local external = {}
        for _, name in ipairs(lsm:List("sound") or {}) do
            local label = tostring(name or "")
            if label ~= "" and not label:match("^Overclock:%s") and label ~= "None" then
                external[#external + 1] = {
                    label = label,
                    value = label,
                }
            end
        end
        table.sort(external, compareChoiceLabels)
        for _, entry in ipairs(external) do
            add(entry.label, entry.value)
        end
    end

    if opts.includeBuiltinIDs then
        local builtinChoices = (media and type(media.BuiltinSoundChoices) == "table" and media.BuiltinSoundChoices)
            or ((PortalAuthority.GetBuiltinSummonAlertSounds and PortalAuthority:GetBuiltinSummonAlertSounds()) or {})
        for _, entry in ipairs(builtinChoices) do
            local label = tostring(entry.label or entry.value or "")
            if label == "Alarm Clock" then
                label = localizedText("COMMON_ALARM_CLOCK", "Alarm Clock")
            elseif label == "Raid Warning" then
                label = localizedText("COMMON_RAID_WARNING", "Raid Warning")
            elseif label == "Ready Check" then
                label = localizedText("COMMON_READY_CHECK", "Ready Check")
            elseif label == "Tell Message" then
                label = localizedText("COMMON_TELL_MESSAGE", "Tell Message")
            end
            add(label, entry.value)
        end
    end

    local normalizedCurrent = normalizeSoundChoiceValue(currentValue)
    if normalizedCurrent ~= nil and normalizedCurrent ~= "" then
        local label = tostring(normalizedCurrent)
        if type(normalizedCurrent) == "string" and normalizedCurrent:find("[/\\]") then
            local basename = normalizedCurrent:match("([^\\]+)$") or normalizedCurrent
            basename = basename:gsub("%.%w+$", "")
            if basename ~= "" then
                label = string.format(localizedText("COMMON_CUSTOM_FORMAT", "Custom: %s"), basename)
            end
        end
        add(label, normalizedCurrent)
    end

    return choices
end

local function getSummonSoundChoices(currentValue)
    local media = getPortalMedia()
    if media and media.GetChoices then
        return media.GetChoices("sound", {
            includeNone = true,
            includeBuiltinIDs = true,
            currentValue = currentValue,
        }) or {}
    end
    return buildFallbackSoundChoices(currentValue, {
        includeNone = true,
        includeBuiltinIDs = true,
    })
end

local function getAvailableFontChoices()
    local media = getPortalMedia()
    local seen, choices = {}, {}
    local bundledKeys = {}
    local deferredExternalByPath = {}
    local deferredExternalOrder = {}

    local function add(label, path)
        if type(path) ~= "string" then return end
        local key = normalizeChoicePathKey(path)
        if not key then return end
        if seen[key] then return end
        seen[key] = true
        table.insert(choices, { label = label, value = path })
    end

    local function queueExternal(label, path)
        if type(path) ~= "string" then
            return
        end
        local key = normalizeChoicePathKey(path)
        if not key or key == "__GLOBAL_FONT__" or bundledKeys[key] then
            return
        end

        local entry = deferredExternalByPath[key]
        if not entry then
            entry = {
                label = tostring(label or path or ""),
                value = path,
            }
            deferredExternalByPath[key] = entry
            deferredExternalOrder[#deferredExternalOrder + 1] = key
            return
        end

        local candidateLabel = tostring(label or path or "")
        if candidateLabel ~= "" and candidateLabel:lower() < tostring(entry.label or ""):lower() then
            entry.label = candidateLabel
        end
    end

    add(localizedText("COMMON_GLOBAL_UI_FONT", "Global UI Font"), "")

    if media and type(media.BundledFonts) == "table" then
        for _, font in ipairs(media.BundledFonts) do
            local path = resolveFontChoicePath(font)
            local key = normalizeChoicePathKey(path)
            if key then
                bundledKeys[key] = true
                add(font.label, path)
            end
        end
    end

    if media and media.GetChoices then
        for _, option in ipairs(media.GetChoices("font", { includeGlobalUi = true }) or {}) do
            local path = resolveFontChoicePath(option)
            queueExternal(tostring(option.label or option.value or path or ""), path)
        end
    end

    for _, font in ipairs(BUILTIN_FONT_CHOICES) do add(font.label, font.value) end

    local lsm = LibStub and LibStub("LibSharedMedia-3.0", true)
    if lsm and lsm.HashTable then
        for label, path in pairs(lsm:HashTable("font") or {}) do
            local normalizedPath = resolveFontChoicePath({ label = label, value = path })
            queueExternal(tostring(label or normalizedPath or ""), normalizedPath)
        end
    end

    local deferredExternal = {}
    for _, key in ipairs(deferredExternalOrder) do
        local entry = deferredExternalByPath[key]
        if entry then
            deferredExternal[#deferredExternal + 1] = entry
        end
    end

    table.sort(deferredExternal, compareChoiceLabels)
    for _, option in ipairs(deferredExternal) do
        add(option.label, option.value)
    end

    return choices
end

local function buildSharedFontPickerChoices()
    local options = {}

    for _, option in ipairs(getAvailableFontChoices() or {}) do
        local value = tostring(option and option.value or "")
        local label = tostring((option and option.label) or value)
        options[#options + 1] = {
            value = value,
            label = label,
            buttonLabel = label,
            secondary = "",
            previewFontPath = (value ~= "") and value or nil,
            aliases = { label },
        }
    end

    if #options == 0 then
        options[1] = {
            value = "",
            label = localizedText("COMMON_GLOBAL_UI_FONT", "Global UI Font"),
            buttonLabel = localizedText("COMMON_GLOBAL_UI_FONT", "Global UI Font"),
            secondary = "",
            previewFontPath = nil,
            aliases = { localizedText("COMMON_GLOBAL_UI_FONT", "Global UI Font") },
        }
    end

    return options
end
local function applyGlobalFontToObject(object, size, flags)
    if not object or not object.SetFont then
        return
    end

    object._paGlobalFontManaged = true
    object._paGlobalFontSize = size
    object._paGlobalFontFlags = flags
    refreshManagedFontObject(object)
end

OptionsInternal.DEBUG_SETTINGS = DEBUG_SETTINGS
OptionsInternal.debugSettingsLog = debugSettingsLog
OptionsInternal.trim = trim
OptionsInternal.rgba255 = rgba255
OptionsInternal.unpackColor = unpackColor
OptionsInternal.FLAT_SETTINGS_BACKDROP = FLAT_SETTINGS_BACKDROP
OptionsInternal.SETTINGS_WINDOW_HOSTED_TOP_RHYTHM_NUDGE = SETTINGS_WINDOW_HOSTED_TOP_RHYTHM_NUDGE
OptionsInternal.SETTINGS_WINDOW_SCROLLBAR_WIDTH = SETTINGS_WINDOW_SCROLLBAR_WIDTH
OptionsInternal.SETTINGS_WINDOW_SCROLLBAR_GAP = SETTINGS_WINDOW_SCROLLBAR_GAP
OptionsInternal.SETTINGS_WINDOW_SCROLLBAR_MIN_THUMB_HEIGHT = SETTINGS_WINDOW_SCROLLBAR_MIN_THUMB_HEIGHT
OptionsInternal.SETTINGS_WINDOW_HOST_COLORS = SETTINGS_WINDOW_HOST_COLORS
OptionsInternal.SETTINGS_WINDOW_SCROLLBAR_TRACK = SETTINGS_WINDOW_SCROLLBAR_TRACK
OptionsInternal.SETTINGS_WINDOW_SCROLLBAR_THUMB = SETTINGS_WINDOW_SCROLLBAR_THUMB
OptionsInternal.SETTINGS_WINDOW_SCROLLBAR_THUMB_HOVER = SETTINGS_WINDOW_SCROLLBAR_THUMB_HOVER
OptionsInternal.getPortalMedia = getPortalMedia
OptionsInternal.normalizeSoundChoiceValue = normalizeSoundChoiceValue
OptionsInternal.compareChoiceLabels = compareChoiceLabels
OptionsInternal.normalizeChoicePathKey = normalizeChoicePathKey
OptionsInternal.resolveFontChoicePath = resolveFontChoicePath
OptionsInternal.getSoundChoiceValueKey = getSoundChoiceValueKey
OptionsInternal.buildFallbackSoundChoices = buildFallbackSoundChoices
OptionsInternal.getSummonSoundChoices = getSummonSoundChoices
OptionsInternal.getAvailableFontChoices = getAvailableFontChoices
OptionsInternal.buildSharedFontPickerChoices = buildSharedFontPickerChoices
OptionsInternal.applyGlobalFontToObject = applyGlobalFontToObject
OptionsInternal.refreshManagedFontObject = refreshManagedFontObject
OptionsInternal.SUMMON_SOUND_CHANNEL_CHOICES = SUMMON_SOUND_CHANNEL_CHOICES
