local _, MSUF = ...

MSUF = MSUF or _G.MSUF_NS or {}
local ExportPublic = MSUF.ExportPublic or function(name, value)
  _G[name] = value
  return value
end

--- UnitFrames/Engine/MSUF_UF_Config.lua
---
--- Cold config compiler for unit frames. It reads MSUF_DB, resolves legacy
--- aliases/defaults, and produces per-unit specs consumed by Factory, Core, and
--- hot Dispatch code. Keep DB walking and schema interpretation here; runtime
--- event handlers should read compiled frame.MSUFSpec fields instead.

local UF = MSUF.UF
UF.Config = UF.Config or {}
local Config = UF.Config

local type = type
local tonumber = tonumber
local tostring = tostring
local pairs = pairs
local byte, sub = string.byte, string.sub
local max, abs, floor = math.max, math.abs, math.floor
local CreateFrame = _G.CreateFrame
local InCombatLockdown = _G.InCombatLockdown
local IsInInstance = _G.IsInInstance
local GetInstanceInfo = _G.GetInstanceInfo
local IsPVPTimerRunning = _G.IsPVPTimerRunning
local UnitIsPVP = _G.UnitIsPVP
local UnitIsPVPFreeForAll = _G.UnitIsPVPFreeForAll
local wipe = _G.wipe or table.wipe or function(t)
    for k in pairs(t) do
        t[k] = nil
    end
    return t
end
local Clamp01 = UF.Clamp01
local Number = UF.NumberWithFallback
local NormalizeDispelDetectTrigger = UF.NormalizeDispelDetectTrigger
local NormalizeDispelOverlayTrigger = UF.NormalizeDispelOverlayTrigger
local NormalizeDispelOverlayStyle = UF.NormalizeDispelOverlayStyle
local NormalizeRangeFadeLayerMode = UF.NormalizeRangeFadeLayerMode
local NormalizeAbsorbTestScope = UF.NormalizeAbsorbTestScope
local AbsorbTextureTestEnabledForScope = UF.AbsorbTextureTestEnabledForScope
local ScopedValue = UF.ConfigScopedValue
local CompileBorderPriority = UF.CompileBorderPriority
local ResolveBarGradient = UF.ResolveBarGradient
local FillPredictionColors = UF.FillPredictionColors

local WHITE = "Interface\\Buttons\\WHITE8x8"
local DEFAULT_FONT = _G.STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF"
local CDM_WIDTH_FRAMES = {
  cooldown = "EssentialCooldownViewer",
  utility = "UtilityCooldownViewer",
  tracked_buffs = "BuffIconCooldownViewer",
}
local COOLDOWN_VIEWER_FRAMES = {
  EssentialCooldownViewer = true,
  UtilityCooldownViewer = true,
  BuffIconCooldownViewer = true,
}
local ECV_ANCHORS = {
  player = { "RIGHT", "LEFT", -20, 0 },
  target = { "LEFT", "RIGHT", 20, 0 },
  focus = { "TOP", "LEFT", 0, 0 },
  targettarget = { "TOP", "RIGHT", 0, -40 },
  focustarget = { "TOP", "RIGHT", 0, 40 },
}

local DEFAULTS = {
  player = { width = 275, height = 40, x = -256, y = -180, showName = false, showPower = true },
  target = { width = 275, height = 40, x = 320, y = -180, showName = true, showPower = true },
  focus = { width = 180, height = 30, x = -260, y = -300, showName = true, showPower = false },
  targettarget = { width = 180, height = 30, x = 220, y = -300, showName = false, showPower = false },
  focustarget = { width = 180, height = 30, x = 260, y = 180, showName = true, showPower = false },
  pet = { width = 220, height = 30, x = -275, y = -250, showName = true, showPower = true },
  boss = { width = 180, height = 30, x = 500, y = 180, showName = true, showPower = false },
}

local POWER_KEYS = {
  player = "showPlayerPowerBar",
  target = "showTargetPowerBar",
  focus = "showFocusPowerBar",
  boss = "showBossPowerBar",
}

local function IsCooldownViewerFrameName(frameName)
  return COOLDOWN_VIEWER_FRAMES[frameName] == true
end

local CASTBAR_KEYS = {
  player = "enablePlayerCastbar",
  target = "enableTargetCastbar",
  focus = "enableFocusCastbar",
  boss = "enableBossCastbar",
}

local RANGE_KEYS = {
  target = true,
  targettarget = true,
  focus = true,
  focustarget = true,
  pet = true,
  boss = true,
}

local CLASS_TOKENS = {
  "WARRIOR", "PALADIN", "HUNTER", "ROGUE", "PRIEST", "DEATHKNIGHT", "SHAMAN",
  "MAGE", "WARLOCK", "MONK", "DRUID", "DEMONHUNTER", "EVOKER",
}

local NPC_COLOR_DEFAULTS = {
  friendly = { 0, 1, 0 },
  neutral = { 1, 1, 0 },
  enemy = { 0.85, 0.10, 0.10 },
  dead = { 0.4, 0.4, 0.4 },
  npcBoss = { 0.74, 0.11, 0 },
  npcMiniboss = { 0.56, 0, 0.74 },
  npcCaster = { 0, 0.45, 0.74 },
  npcMelee = { 0.99, 0.99, 0.99 },
  npcRegular = { 0.70, 0.56, 0.33 },
}

local dbInitialized = false

--- Config can be asked for specs before every module has finished loading.
--- EnsureDB centralizes that bootstrap without making the hot dispatch layer
--- know about profile initialization.
local function EnsureDB()
  if not dbInitialized or type(_G.MSUF_DB) ~= "table" then
    if type(_G.MSUF_InitProfiles) == "function" then
      _G.MSUF_InitProfiles()
    end
    if type(_G.MSUF_EnsureDB) == "function" then
      _G.MSUF_EnsureDB()
    end
    dbInitialized = true
  end
  if type(_G.MSUF_DB) ~= "table" then
    ExportPublic("MSUF_DB", {})
  end
  return _G.MSUF_DB
end

local function Bool(value, fallback)
    if value == nil then
        return fallback
    end
    return value == true
end

local function OutlineModeEnabled(value, fallback)
  if value == nil then value = fallback end
  if value == true or value == false then return value end
  value = tonumber(value)
  if value == nil then return fallback == true end
  return value == 1
end

local function NormalizeFrameOutlineStrata(value)
  local normalize = _G.MSUF_NormalizeFrameStrata
  if type(normalize) == "function" then return normalize(value, "AUTO") end
  if value == nil or value == "" then return "AUTO" end
  value = tostring(value):upper()
  local rank = _G.MSUF_FRAME_STRATA_RANK
  return rank and rank[value] and value or "AUTO"
end

local function CopyColor(dst, r, g, b, a)
  dst.r = Number(r, dst.r or 1)
  dst.g = Number(g, dst.g or 1)
  dst.b = Number(b, dst.b or 1)
  dst.a = Number(a, dst.a or 1)
end

local function CopyConfigColor(src)
  if type(src) ~= "table" then
    return nil
  end
  local r = tonumber(src.r or src[1])
  local g = tonumber(src.g or src[2])
  local b = tonumber(src.b or src[3])
  local a = src.a
  if a == nil then a = src[4] end
  a = tonumber(a)
  if r == nil or g == nil or b == nil then
    return nil
  end
  return { r = r, g = g, b = b, a = a or 1 }
end

local function ApplyNpcTypeFlags(dst, general, colorKey)
  dst.npcColorMode = general.npcColorMode == "type" and "type" or "reaction"
  dst[colorKey] = general[colorKey] ~= false
  dst.npcTypeTarget = general.npcTypeTarget ~= false
  dst.npcTypeFocus = general.npcTypeFocus ~= false
  dst.npcTypeBoss = general.npcTypeBoss ~= false
  dst.npcTypeToT = general.npcTypeToT ~= false
end

local function NPCTypeTextColorEnabled(text)
  return text and text.npcColorMode == "type" and text.npcTypeColorText ~= false
end

local function CopyPowerColorOverrides(dst, src, onlyMissing)
  if type(src) ~= "table" then
    return
  end
  for token, color in pairs(src) do
    if (not onlyMissing or dst[token] == nil) and type(color) == "table" then
      local r = tonumber(color.r or color[1])
      local g = tonumber(color.g or color[2])
      local b = tonumber(color.b or color[3])
      if r and g and b then
        dst[token] = { r = r, g = g, b = b }
      end
    end
  end
end

local DIRECT_TEXT_LAYOUTS = {
  "Name", "HealthLeft", "HealthCenter", "HealthRight",
  "PowerLeft", "PowerCenter", "PowerRight",
}

local function CopyDirectTextLayout(text, conf)
  for i = 1, #DIRECT_TEXT_LAYOUTS do
    local suffix = DIRECT_TEXT_LAYOUTS[i]
    local key = "direct" .. suffix
    text[key .. "Point"] = conf[key .. "Point"]
    text[key .. "RelativePoint"] = conf[key .. "RelativePoint"]
    text[key .. "X"] = Number(conf[key .. "OffsetX"], 0)
    text[key .. "Y"] = Number(conf[key .. "OffsetY"], 0)
    text[key .. "Color"] = CopyConfigColor(conf[key .. "Color"])
  end
end

local function ClearDirectTextLayout(text)
  for i = 1, #DIRECT_TEXT_LAYOUTS do
    local key = "direct" .. DIRECT_TEXT_LAYOUTS[i]
    text[key .. "Point"] = nil
    text[key .. "RelativePoint"] = nil
    text[key .. "X"] = nil
    text[key .. "Y"] = nil
    text[key .. "Color"] = nil
  end
end

local TEXT_SIDES = { "Left", "Center", "Right" }

local function ApplySideTextOffsets(text, outPrefix, confPrefix, legacyPrefix, baseX, baseY, conf, general)
  for i = 1, #TEXT_SIDES do
    local side = TEXT_SIDES[i]
    local outKey = outPrefix .. side
    text[outKey .. "X"] = baseX + Number(conf[confPrefix .. side .. "OffsetX"] or conf[legacyPrefix .. side .. "OffsetX"]
      or general[confPrefix .. side .. "OffsetX"] or general[legacyPrefix .. side .. "OffsetX"], 0)
    text[outKey .. "Y"] = baseY + Number(conf[confPrefix .. side .. "OffsetY"] or conf[legacyPrefix .. side .. "OffsetY"]
      or general[confPrefix .. side .. "OffsetY"] or general[legacyPrefix .. side .. "OffsetY"], 0)
  end
end

local function ResolveBarMode(general)
  local mode = general and general.barMode
  if type(mode) == "string" then
    mode = mode:lower()
  end
  if mode ~= "dark" and mode ~= "class" and mode ~= "unified" and mode ~= "gradient" then
    if general and general.useClassColors == true then
      mode = "class"
    elseif general and general.darkMode == true then
      mode = "dark"
    else
      mode = "dark"
    end
  end
  if mode == "gradient" and general and general.enableHealthGradient == false then
    mode = "class"
  end
  return mode
end

local HEALTH_MODE_ALIASES = {
  CLASS = "class", class = "class",
  GRADIENT = "gradient", gradient = "gradient",
  DARK = "dark", dark = "dark",
  UNIFIED = "unified", unified = "unified",
}

local function ResolveUnitBarMode(conf, general)
  local mode = conf and conf.healthColorMode
  if type(mode) == "string" then
    mode = HEALTH_MODE_ALIASES[mode] or HEALTH_MODE_ALIASES[mode:lower()]
  end
  return mode or ResolveBarMode(general)
end

local function ResolvePowerMode(general)
  local mode = general and (general.powerColorMode or general.powerBarColorMode)
  if type(mode) == "string" then
    mode = mode:lower()
  end
  if mode == "class" or mode == "static" or mode == "unified" or mode == "dark" then
    return mode
  end
  return "power"
end

local function ResolveDarkColor(general, dst)
  local gray = Number(general and (general.darkBarGray or general.darkBgBrightness), 0.07)
  if gray > 1 then
    gray = gray / 100
  end
  CopyColor(dst, general and general.darkBarR or gray, general and general.darkBarG or gray, general and general.darkBarB or gray, 1)
end

local function ResolveTextColor(general, dst)
  dst = dst or {}
  local getColor = _G.MSUF_GetConfiguredFontColor or MSUF.MSUF_GetConfiguredFontColor
  if type(getColor) == "function" then
    local r, g, b = getColor()
    if type(r) == "number" and type(g) == "number" and type(b) == "number" then
      CopyColor(dst, r, g, b, 1)
      return dst
    end
  end
  if general and general.useCustomFontColor == true
    and type(general.fontColorCustomR) == "number"
    and type(general.fontColorCustomG) == "number"
    and type(general.fontColorCustomB) == "number" then
    CopyColor(dst, general.fontColorCustomR, general.fontColorCustomG, general.fontColorCustomB, 1)
    return dst
  end
  local palette = MSUF.MSUF_FONT_COLORS or _G.MSUF_FONT_COLORS
  local key = general and general.fontColor
  if type(key) == "string" and palette then
    local c = palette[key:lower()]
    if c then
      CopyColor(dst, c.r or c[1], c.g or c[2], c.b or c[3], c.a or c[4] or 1)
      return dst
    end
  end
  CopyColor(dst, 1, 1, 1, 1)
  return dst
end

local function ResolveBgAlpha(general, bars, conf)
  local perUnit = conf and conf.hpBgAlpha
  if type(perUnit) == "number" then
    return Clamp01(perUnit, 0.85)
  end
  local alpha = bars and bars.barBackgroundAlpha
  if type(alpha) == "number" then
    return Clamp01(alpha / 100, 0.9)
  end
  return Clamp01(general and general.barBackgroundAlpha, 0.9)
end

local function ResolvePowerBgAlpha(general, bars, conf)
  local perUnit = conf and conf.powerBarBgAlpha
  if type(perUnit) == "number" then
    return Clamp01(perUnit, 0.85)
  end
  return ResolveBgAlpha(general, bars, conf)
end

local function DarkTint(general, r, g, b)
  if general and general.darkMode == true and general.darkBgCustomColor ~= true then
    local brightness = Clamp01(general.darkBgBrightness, 0.25)
    return (r or 0) * brightness, (g or 0) * brightness, (b or 0) * brightness
  end
  return r or 0, g or 0, b or 0
end

local function ResolveHealthBackground(general, bars, health, dst, conf)
  dst = dst or {}
  local r, g, b, a
  local getBg = _G.MSUF_GetBarBackgroundTintRGBA
  if type(getBg) == "function" then
    r, g, b, a = getBg()
  else
    r, g, b = DarkTint(general, Number(general and general.classBarBgR, 0), Number(general and general.classBarBgG, 0), Number(general and general.classBarBgB, 0))
    a = 0.9
  end
  if general and general.barBgMatchHPColor == true and health then
    r, g, b = DarkTint(general, health.r, health.g, health.b)
  end
  CopyColor(dst, r, g, b, (Number(a, 0.9)) * ResolveBgAlpha(general, bars, conf))
  return dst
end

local function ResolvePowerBackground(general, bars, health, dst, conf)
  dst = dst or {}
  local r, g, b, a
  local getBg = _G.MSUF_GetPowerBarBackgroundTintRGBA
  if type(getBg) == "function" then
    r, g, b, a = getBg()
  else
    r, g, b = DarkTint(general, Number(general and general.powerBarBgColorR, Number(general and general.classBarBgR, 0)), Number(general and general.powerBarBgColorG, Number(general and general.classBarBgG, 0)), Number(general and general.powerBarBgColorB, Number(general and general.classBarBgB, 0)))
    a = 0.9
  end
  if (general and general.powerBarBgMatchBarColor == true) or (bars and bars.powerBarBgMatchBarColor == true) then
    r, g, b = DarkTint(general, health and health.r or r, health and health.g or g, health and health.b or b)
  end
  CopyColor(dst, r, g, b, (Number(a, 0.9)) * ResolvePowerBgAlpha(general, bars, conf))
  return dst
end

local SetAbsorbTextureTestMode = _G.MSUF_SetAbsorbTextureTestMode or function(enabled, scope)
  ExportPublic("MSUF_AbsorbTextureTestMode", enabled == true)
  ExportPublic("MSUF_AbsorbTextureTestScope", enabled and NormalizeAbsorbTestScope(scope) or nil)
end
ExportPublic("MSUF_SetAbsorbTextureTestMode", SetAbsorbTextureTestMode)

local ClearAbsorbTextureTestMode = _G.MSUF_ClearAbsorbTextureTestMode or function()
  ExportPublic("MSUF_AbsorbTextureTestMode", false)
  ExportPublic("MSUF_AbsorbTextureTestScope", nil)
end
ExportPublic("MSUF_ClearAbsorbTextureTestMode", ClearAbsorbTextureTestMode)

local ShouldShowAbsorbTextureTest = _G.MSUF_ShouldShowAbsorbTextureTest or function(frame, scope)
  local key = scope
    or frame and (frame.configKey or frame.MSUFUnitKey or frame._msufGFKind or frame.unitKey)
    or nil
  return AbsorbTextureTestEnabledForScope(key)
end
ExportPublic("MSUF_ShouldShowAbsorbTextureTest", ShouldShowAbsorbTextureTest)

local HEALTH_TEXT_MODE_ALIASES = {
  FULL_ONLY = "CURRENT",
  PERCENT_ONLY = "PERCENT",
  FULL_SLASH_MAX = "CURMAX",
  FULL_PLUS_PERCENT = "CURPERCENT",
  PERCENT_PLUS_FULL = "PERCENTCUR",
}
local POWER_TEXT_MODE_ALIASES = {
  FULL_ONLY = "CURRENT",
  PERCENT_ONLY = "PERCENT",
  FULL_SLASH_MAX = "CURMAX",
  FULL_PLUS_PERCENT = "CURPERCENT",
  PERCENT_PLUS_FULL = "CURPERCENT",
}

local function NormalizeHealthTextMode(mode, fallback)
  return mode == nil and fallback or HEALTH_TEXT_MODE_ALIASES[mode] or mode
end

local function NormalizePowerTextMode(mode, fallback)
  return mode == nil and fallback or POWER_TEXT_MODE_ALIASES[mode] or mode
end

local function ResolveTextSlotHidePercentSymbol(conf, general, key)
  if conf and conf[key] ~= nil then
    return conf[key] == true
  end
  return general and general.hidePercentSymbol == true
end

local function ResolveNameShortening(db, general, conf, unit, text)
  local shorten = db and db.shortenNames == true
  local maxChars = Number(general and general.shortenNameMaxChars, 6)
  local side = tostring(general and general.shortenNameClipSide or "LEFT"):upper()
  local maskPx = Number(general and general.shortenNameFrontMaskPx, 0)
  local dots = general and general.shortenNameShowDots
  dots = dots == nil or dots == true

  if conf and conf.fontOverride == true then
    if conf.shortenNames ~= nil then
      shorten = conf.shortenNames == true
    elseif conf.nameShortenEnabled ~= nil then
      shorten = conf.nameShortenEnabled == true
    end
    maxChars = Number(conf.shortenNameMaxChars or conf.nameMaxChars, maxChars)
    side = tostring(conf.shortenNameClipSide or conf.nameClipSide or side):upper()
    maskPx = Number(conf.shortenNameFrontMaskPx, maskPx)
    if conf.shortenNameShowDots ~= nil then
      dots = conf.shortenNameShowDots == true
    elseif conf.nameNoEllipsis ~= nil then
      dots = conf.nameNoEllipsis ~= true
    end
  end

  if unit == "player" and not (conf and conf.fontOverride == true and (conf.shortenNames == true or conf.nameShortenEnabled == true)) then
    shorten = false
  end
  if side ~= "RIGHT" then
    side = "LEFT"
  end

  maxChars = floor((tonumber(maxChars) or 6) + 0.5)
  if maxChars < 4 then
    maxChars = 4
  elseif maxChars > 40 then
    maxChars = 40
  end
  maskPx = floor((tonumber(maskPx) or 0) + 0.5)
  if maskPx < 0 then
    maskPx = 0
  elseif maskPx > 80 then
    maskPx = 80
  end

  text.nameShorten = shorten == true
  text.nameShortenMax = maxChars
  text.nameShortenSide = side
  text.nameShortenDots = dots == true
  text.nameShortenMaskPx = maskPx
end

local function ResolveNameColorFlags(general, conf)
  local classColor = general and general.nameClassColor == true
  local npcColor = general and general.npcNameRed == true
  local npcClassColor = general and general.nameNpcClassColor == true
  if conf and conf.fontOverride == true then
    if conf.nameClassColor ~= nil then
      classColor = conf.nameClassColor == true
    end
    if conf.npcNameRed ~= nil then
      npcColor = conf.npcNameRed == true
    end
    if conf.nameNpcClassColor ~= nil then
      npcClassColor = conf.nameNpcClassColor == true
    end
  end
  return classColor, npcColor, npcClassColor
end

local function ResolveHealthTextColorByHealth(general, conf)
  local enabled = general and general.colorHealthTextByHealth == true
  if conf and conf.fontOverride == true and conf.colorHealthTextByHealth ~= nil then
    enabled = conf.colorHealthTextByHealth == true
  end
  return enabled
end

local function ResolvePowerTextColorByType(general, conf)
  local enabled = general and general.colorPowerTextByType == true
  if conf and conf.fontOverride == true then
    if conf.powerTextColorByType ~= nil then
      enabled = conf.powerTextColorByType == true
    elseif conf.colorPowerTextByType ~= nil then
      enabled = conf.colorPowerTextByType == true
    end
  end
  return enabled
end

local function Utf8Prefix(value, maxChars)
  value = tostring(value or "")
  maxChars = tonumber(maxChars) or 0
  if value == "" or maxChars <= 0 then
    return ""
  end
  local pos, len, chars = 1, #value, 0
  while pos <= len and chars < maxChars do
    local b = byte(value, pos)
    if not b then
      break
    elseif b < 128 then
      pos = pos + 1
    elseif b < 224 then
      pos = pos + 2
    elseif b < 240 then
      pos = pos + 3
    else
      pos = pos + 4
    end
    chars = chars + 1
  end
  return sub(value, 1, pos - 1)
end

local function ResolveToTInlineSeparator(conf)
  local token = conf and conf.totInlineSeparator
  if token == "__CUSTOM__" then
    token = tostring(conf and conf.totInlineCustomSeparator or ""):gsub("[%c]", " ")
    token = Utf8Prefix(token, 5)
    if token == "" then
      token = " "
    end
  elseif token == nil then
    token = "|"
  else
    token = tostring(token)
  end
  if token == "" then
    token = " "
  end
  return " " .. token .. " "
end

local function ResolveToTInlineColorMode(value)
  value = tostring(value or "AUTO"):upper()
  if value == "TOT_NAME" or value == "TARGET_NAME" or value == "NPC" or value == "DEFAULT" then
    return value
  end
  return "AUTO"
end

local function ResolveToTInline(db, general, unit, targetText)
  if unit ~= "target" then
    targetText.inlineToT = nil
    return
  end

  local tot = type(db and db.targettarget) == "table" and db.targettarget or type(db and db.tot) == "table" and db.tot or nil
  if not (tot and tot.showToTInTargetName == true) then
    targetText.inlineToT = nil
    return
  end

  local inline = targetText.inlineToT
  if type(inline) ~= "table" then
    inline = {}
    targetText.inlineToT = inline
  end
  ResolveNameShortening(db, general, tot, "targettarget", inline)
  inline.enabled = true
  inline.unit = "targettarget"
  inline.separator = ResolveToTInlineSeparator(tot)
  inline.colorMode = ResolveToTInlineColorMode(tot.totInlineColorMode)
  local targetNpcTypeColor = NPCTypeTextColorEnabled(targetText)
  inline.targetNameClassColor = targetText.nameClassColor == true
  inline.targetNameNpcColor = targetText.nameNpcColor == true or targetNpcTypeColor
  inline.targetNameNpcClassColor = targetText.nameNpcClassColor == true
  inline.totNameClassColor, inline.totNameNpcColor, inline.totNameNpcClassColor = ResolveNameColorFlags(general, tot)
  inline.totNameNpcColor = inline.totNameNpcColor == true or targetNpcTypeColor
end

local function NormalizePortraitMode(conf)
  local mode = conf and conf.portraitMode
  if mode == "LEFT" or mode == "RIGHT" then
    return mode
  end
  if conf and conf.showPortrait == true then
    return "LEFT"
  end
  return "OFF"
end

local function NormalizePortraitRender(mode)
  return mode == "CLASS" and "CLASS" or "2D"
end

local function NormalizePortraitClassStyle(value)
  local fn = _G.MSUF_NormalizePortraitClassStyleValue
  if type(fn) == "function" then
    return fn(value)
  end
  local PM = MSUF and MSUF.PortraitMedia
  if PM and type(PM.NormalizeClassPack) == "function" then
    return PM.NormalizeClassPack(value)
  end
  if value == "RONDO_COLOR" or value == "RONDO_WOW" or value == "BLIZZARD" then
    return value
  end
  return "BLIZZARD"
end

local function NormalizePortraitShape(shape)
  if shape == "CIRCLE" or shape == "ROUNDED" or shape == "DIAMOND" then
    return shape
  end
  return "SQUARE"
end

local function NormalizePortraitBorder(style)
  if style == "SOLID" or style == "CLASS_COLOR" or style == "REACTION" or style == "CUSTOM" then
    return style
  end
  return "NONE"
end

local function NormalizePortraitZoom(value)
  value = Number(value, 100)
  if value > 1 and value <= 2 then
    value = value * 100
  end
  if value < 100 then
    return 100
  elseif value > 200 then
    return 200
  end
  return value
end

local function CompilePortraitTexCoords(p, zoom)
  local span = 0.84 * (100 / zoom)
  local inset = (1 - span) * 0.5
  p.zoom = zoom
  p.texL = inset
  p.texR = 1 - inset
  p.texT = inset
  p.texB = 1 - inset
end

local function CompileRange(out, conf, general, key)
  local range = out.range or {}
  out.range = range
  local supported = RANGE_KEYS[key] == true
  range.enabled = supported and conf.rangeFadeEnabled ~= false
  range.active = out.enabled ~= false and range.enabled == true
  range.alpha = Clamp01(conf.rangeFadeAlpha or general.rangeFadeAlpha, 0.4)
  range.layerMode = NormalizeRangeFadeLayerMode(conf.rangeFadeLayerMode or general.rangeFadeLayerMode)
end

local function CompileAlpha(out, conf, general, key)
  local alpha = out.alpha or {}
  out.alpha = alpha

  local hpAlpha = Clamp01(conf.hpBarAlpha, 1)
  alpha.hpAlpha = hpAlpha
  alpha.excludeTextPortrait = conf.alphaExcludeTextPortrait == true
  alpha.active = hpAlpha < 1
end

local function ClampStatusLayer(value, fallback)
  value = Number(value, fallback or 7)
  value = math.floor(value + 0.5)
  if value < 0 then
    return 0
  elseif value > 30 then
    return 30
  end
  return value
end

local function StatusBool(conf, general, key, fallback, legacyKey)
  local value = conf and conf[key]
  if value == nil and legacyKey then
    value = conf and conf[legacyKey]
  end
  if value == nil then
    value = general and general[key]
    if value == nil and legacyKey then
      value = general and general[legacyKey]
    end
  end
  return Bool(value, fallback)
end

local Secrets = MSUF.Secrets or {}
local PlainTrue = Secrets.PlainTrue or function(value) return value == true or value == 1 end

local function APIBool(fn, ...)
  if type(fn) ~= "function" then
    return false
  end
  return PlainTrue(fn(...)) == true
end

local function CurrentInstanceType()
  if IsInInstance then
    local _, instanceType = IsInInstance()
    if type(instanceType) == "string" and instanceType ~= "" then
      return instanceType
    end
  end
  if GetInstanceInfo then
    local _, instanceType = GetInstanceInfo()
    if type(instanceType) == "string" and instanceType ~= "" then
      return instanceType
    end
  end
  return nil
end

local _pvpContextKnown, _pvpContextActive = false, false

local function ComputePVPIndicatorContextActive()
  local instanceType = CurrentInstanceType()
  if instanceType == "pvp" or instanceType == "arena" then
    return true
  elseif instanceType == "party" or instanceType == "raid" then
    return false
  end

  local cpvp = _G.C_PvP
  if cpvp then
    if APIBool(cpvp.IsWarModeActive) or APIBool(cpvp.IsWarModeDesired) then
      return true
    end
  end
  return APIBool(UnitIsPVPFreeForAll, "player")
    or APIBool(UnitIsPVP, "player")
    or APIBool(IsPVPTimerRunning)
end

function UF.InvalidatePVPIndicatorContext()
  _pvpContextKnown = false
end

function UF.PVPIndicatorContextActive()
  if not _pvpContextKnown then
    _pvpContextActive = ComputePVPIndicatorContextActive() == true
    _pvpContextKnown = true
  end
  return _pvpContextActive == true
end

local PVP_CONTEXT_REFRESH_ELEMENTS = { "StatusIndicators", "PVPIndicator", "GroupStatusRuntime" }

function UF.RefreshPVPIndicatorContext(reason, force)
  local oldKnown, oldActive = _pvpContextKnown, _pvpContextActive
  UF.InvalidatePVPIndicatorContext()
  local active = UF.PVPIndicatorContextActive()
  if force ~= true and oldKnown and oldActive == active then
    return false
  end
  if UF.RefreshElements then
    UF.RefreshElements(nil, PVP_CONTEXT_REFRESH_ELEMENTS, reason or "MSUF_PVP_CONTEXT")
  end
  local gf = MSUF.GF
  if gf then
    if gf.RefreshVisuals then
      gf.RefreshVisuals(nil, gf.DIRTY_VISUAL)
    elseif gf.RefreshAll then
      gf.RefreshAll()
    end
  end
  return true
end

local function RegisterPVPContextEvent(frame, event, unit)
  if unit then
    frame:RegisterUnitEvent(event, unit)
  else
    frame:RegisterEvent(event)
  end
end

if not UF.pvpIndicatorContextDriver then
  local pvpDriver = CreateFrame("Frame")
  pvpDriver:SetScript("OnEvent", function(_, event, unit)
    if unit and unit ~= "player" then
      return
    end
    UF.RefreshPVPIndicatorContext("MSUF_PVP_CONTEXT_" .. tostring(event), event == "PLAYER_ENTERING_WORLD")
  end)
  RegisterPVPContextEvent(pvpDriver, "PLAYER_ENTERING_WORLD")
  RegisterPVPContextEvent(pvpDriver, "ZONE_CHANGED_NEW_AREA")
  RegisterPVPContextEvent(pvpDriver, "PVP_TIMER_UPDATE")
  RegisterPVPContextEvent(pvpDriver, "PLAYER_FLAGS_CHANGED")
  RegisterPVPContextEvent(pvpDriver, "WAR_MODE_STATUS_UPDATE")
  RegisterPVPContextEvent(pvpDriver, "UNIT_FACTION", "player")
  UF.pvpIndicatorContextDriver = pvpDriver
end

local function StatusNumber(conf, general, key, fallback, legacyKey)
  local value = conf and conf[key]
  if value == nil and legacyKey then
    value = conf and conf[legacyKey]
  end
  if value == nil then
    value = general and general[key]
    if value == nil and legacyKey then
      value = general and general[legacyKey]
    end
  end
  return Number(value, fallback)
end

local function StatusString(conf, general, key, fallback, legacyKey)
  local value = conf and conf[key]
  if (type(value) ~= "string" or value == "") and legacyKey then
    value = conf and conf[legacyKey]
  end
  if type(value) ~= "string" or value == "" then
    value = general and general[key]
    if (type(value) ~= "string" or value == "") and legacyKey then
      value = general and general[legacyKey]
    end
  end
  if type(value) ~= "string" or value == "" then
    value = fallback
  end
  return value or ""
end

local function StatusAllowed(key, id)
  if id == "leader" or id == "assist" or id == "combat" or id == "incomingRes" then
    return key == "player" or key == "target"
  elseif id == "pvp" then
    return key == "player" or key == "target" or key == "focus" or key == "targettarget" or key == "focustarget"
  elseif id == "resting" then
    return key == "player"
  elseif id == "raidGroup" then
    return key == "player" or key == "target" or key == "targettarget" or key == "focustarget" or key == "focus"
  elseif id == "elite" then
    return key == "target" or key == "focus" or key == "targettarget" or key == "focustarget" or key == "boss"
  end
  return true
end

local function ResetList(list)
  list = list or {}
  wipe(list)
  return list
end

local function AddEvent(list, event)
  list[#list + 1] = event
end

local function CompileStatusEntry(status, id, conf, general, key, showKey, fallbackShow, sizeKey, fallbackSize, anchorKey, fallbackAnchor, xKey, fallbackX, yKey, fallbackY, layerKey, fallbackLayer)
  local entry = status[id] or {}
  status[id] = entry
  entry.enabled = StatusAllowed(key, id) and StatusBool(conf, general, showKey, fallbackShow) or false
  entry.size = StatusNumber(conf, general, sizeKey, fallbackSize)
  entry.anchor = StatusString(conf, general, anchorKey, fallbackAnchor)
  entry.x = StatusNumber(conf, general, xKey, fallbackX)
  entry.y = StatusNumber(conf, general, yKey, fallbackY)
  entry.layer = ClampStatusLayer(StatusNumber(conf, general, layerKey, fallbackLayer), fallbackLayer)
  return entry
end

local function StatusEntryDef(id, showKey, showDefault, sizeKey, sizeDefault, anchorKey, anchorDefault, xKey, xDefault, yKey, yDefault, layerKey, layerDefault, style, symbol, customIcon)
  return { id, showKey, showDefault, sizeKey, sizeDefault, anchorKey, anchorDefault, xKey, xDefault, yKey, yDefault, layerKey, layerDefault, style = style, symbol = symbol, customIcon = customIcon }
end

local function PrefixedStatusDef(id, showKey, showDefault, prefix, sizeDefault, anchorDefault, xDefault, yDefault, layerDefault, style, symbol, customIcon)
  return StatusEntryDef(id, showKey, showDefault,
    prefix .. "Size", sizeDefault, prefix .. "Anchor", anchorDefault,
    prefix .. "OffsetX", xDefault, prefix .. "OffsetY", yDefault,
    prefix .. "Layer", layerDefault, style, symbol, customIcon)
end

local UNIT_STATUS_ENTRY_DEFS = {
  PrefixedStatusDef("leader", "showLeaderIcon", true, "leaderIcon", 14, "TOPLEFT", 0, 3, 7, { "leaderIconStyle", "BLIZZARD" }, nil, { "leaderIconCustomIcon", "" }),
  PrefixedStatusDef("assist", "showLeaderIcon", true, "leaderIcon", 14, "TOPLEFT", 0, 3, 7, { "assistIconStyle", "BLIZZARD", "leaderIconStyle" }, nil, { "assistIconCustomIcon", "" }),
  PrefixedStatusDef("raidMarker", "showRaidMarker", true, "raidMarker", 18, "TOPLEFT", 16, 3, 7, nil, nil, { "raidMarkerCustomIcon", "" }),
  PrefixedStatusDef("level", "showLevelIndicator", true, "levelIndicator", 14, "NAMERIGHT", 0, 0, 7),
  StatusEntryDef("raidGroup", "showRaidGroupInName", false, "nameFontSize", 12, "raidGroupNameAnchor", "NAMERIGHT", "raidGroupNameOffsetX", 3, "raidGroupNameOffsetY", 0, "nameTextLayer", 5, { "raidGroupNameStyle", "PAREN" }),
  PrefixedStatusDef("elite", "showEliteIcon", true, "eliteIcon", 20, "TOPRIGHT", 2, 2, 7, nil, nil, { "eliteIconCustomIcon", "" }),
  PrefixedStatusDef("statusText", "statusTextEnabled", true, "statusText", 14, "CENTER", 0, 0, 7),
  PrefixedStatusDef("combat", "showCombatStateIndicator", true, "combatStateIndicator", 18, "TOPLEFT", 0, 0, 7, nil, { "combatStateIndicatorSymbol", "DEFAULT" }, { "combatStateIndicatorCustomIcon", "" }),
  PrefixedStatusDef("resting", "showRestingIndicator", false, "restedStateIndicator", 18, "TOPLEFT", 0, 0, 7, nil, { "restedStateIndicatorSymbol", "DEFAULT", "restingStateIndicatorSymbol" }, { "restedStateIndicatorCustomIcon", "" }),
  PrefixedStatusDef("incomingRes", "showIncomingResIndicator", true, "incomingResIndicator", 18, "TOPRIGHT", 0, 0, 7, nil, { "incomingResIndicatorSymbol", "DEFAULT" }, { "incomingResIndicatorCustomIcon", "" }),
  PrefixedStatusDef("pvp", "showPvpIndicator", true, "pvpIndicator", 18, "TOPRIGHT", 0, 0, 7, nil, nil, { "pvpIndicatorCustomIcon", "" }),
}

local function CompileStatusEntryDef(status, conf, general, key, def, fallbackSize)
  local entry = CompileStatusEntry(status, def[1], conf, general, key,
    def[2], def[3], def[4], fallbackSize or def[5], def[6], def[7],
    def[8], def[9], def[10], def[11], def[12], def[13])
  local style = def.style
  if style then
    entry.style = StatusString(conf, general, style[1], style[2])
  end
  local symbol = def.symbol
  if symbol then
    entry.symbol = StatusString(conf, general, symbol[1], symbol[2], symbol[3])
  end
  local customIcon = def.customIcon
  if customIcon then
    entry.customIcon = StatusString(conf, general, customIcon[1], customIcon[2])
  end
  return entry
end

local LOAD_CONDITION_KEYS = {
  { "hideMounted", "HideMounted" },
  { "hideOutOfCombat", "HideOutOfCombat" },
  { "hideSolo", "HideSolo" },
  { "hideInVehicle", "HideInVehicle" },
  { "hideInGroup", "HideInGroup" },
  { "hideInInstance", "HideInInstance" },
  { "hideResting", "HideResting" },
  { "hideInCombat", "HideInCombat" },
  { "hideStealthed", "HideStealthed" },
  { "hideInHousing", "HideInHousing" },
}

local function CompileLoadConditions(out, conf)
  local load = out.load or {}
  out.load = load
  local active = false
  for i = 1, #LOAD_CONDITION_KEYS do
    local def = LOAD_CONDITION_KEYS[i]
    local enabled = Bool(conf["loadCond" .. def[2]], false)
    load[def[1]] = enabled
    active = active or enabled
  end
  load.active = active

  load.unitlessEvents = ResetList(load.unitlessEvents)
  if load.active then
    AddEvent(load.unitlessEvents, "PLAYER_REGEN_ENABLED")
  end
  if load.hideInInstance == true or load.hideInHousing == true then
    AddEvent(load.unitlessEvents, "PLAYER_ENTERING_WORLD")
    AddEvent(load.unitlessEvents, "ZONE_CHANGED_NEW_AREA")
  end
end

local function TextureFromGlobal()
  local fn = _G.MSUF_GetBarTexture
  if type(fn) == "function" then
    return fn() or WHITE
  end
  return WHITE
end

local function BackgroundTextureFromGlobal()
  local fn = _G.MSUF_GetBarBackgroundTexture
  if type(fn) == "function" then
    return fn() or WHITE
  end
  return WHITE
end

local function ResolveStatusbarTextureKey(key, fallback)
  if type(key) == "string" and key ~= "" then
    local resolve = _G.MSUF_ResolveStatusbarTextureKey
    local texture = type(resolve) == "function" and resolve(key) or nil
    if type(texture) == "string" and texture ~= "" then
      return texture
    end
  end
  return fallback or WHITE
end

local function TextureFromScope(conf, general)
  -- Unit-frame textures are shared-only. Party/Raid texture overrides are
  -- resolved by the GroupFrames compiler path.
  local key = general and general.barTexture
  if key ~= nil then
    return ResolveStatusbarTextureKey(key, TextureFromGlobal())
  end
  return TextureFromGlobal()
end

local function BackgroundTextureFromScope(conf, general, foregroundTexture)
  local key
  if general then
    key = general.barBackgroundTexture
    if key == nil then key = general.barBgTexture end
  end
  if key ~= nil then
    if key == "" then
      return foregroundTexture or TextureFromScope(conf, general)
    end
    return ResolveStatusbarTextureKey(key, foregroundTexture or WHITE)
  end
  return BackgroundTextureFromGlobal()
end

local function DetachedPowerTextureFromBars(bars, fallback)
  local key = bars and bars.detachedPowerBarTexture
  if type(key) == "string" and key ~= "" then
    return ResolveStatusbarTextureKey(key, fallback or TextureFromGlobal())
  end
  return fallback or TextureFromGlobal()
end

local function DetachedPowerBackgroundTextureFromBars(bars, foregroundTexture, fallback)
  local key = bars and bars.detachedPowerBarBgTexture
  if type(key) == "string" then
    if key == "" then
      return foregroundTexture or fallback or TextureFromGlobal()
    end
    return ResolveStatusbarTextureKey(key, foregroundTexture or fallback or WHITE)
  end
  return fallback or foregroundTexture or BackgroundTextureFromGlobal()
end

local function ClassPowerFallbackWidth(out, bars)
  local widthMode = bars and bars.classPowerWidthMode or "player"
  if widthMode == "auto_pips" then
    local shape = tostring((bars and bars.classPowerShape) or "BAR"):upper()
    if shape == "CIRCLE" or shape == "DIAMOND" or shape == "HEX" then
      local container = _G.MSUF_ClassPowerContainer
      local liveW = container and container.GetWidth and container:GetWidth() or nil
      if liveW and liveW >= 20 then
        return liveW
      end
    end
  end
  if widthMode == "custom" then
    local custom = Number(bars and bars.classPowerWidth, 0)
    if custom >= 30 then
      return custom
    end
  end
  return max(1, Number(out and out.width, 275) - 4)
end

local function CooldownWidthFrameName(mode)
  return CDM_WIDTH_FRAMES[mode]
end

local function NormalizeClassPowerShape(value)
  value = tostring(value or "BAR"):upper()
  if value == "CIRCLE" or value == "DIAMOND" or value == "HEX" then return value end
  return "BAR"
end

local function ResolveDetachedPowerShape(conf, bars)
  local value = tostring((conf and conf.detachedPowerBarShape) or "FOLLOW_CLASS"):upper()
  if value == "BAR" or value == "ROUND" or value == "CRYSTAL" or value == "ORB" then return value end
  if value ~= "FOLLOW_CLASS" then value = "FOLLOW_CLASS" end
  local classShape = NormalizeClassPowerShape(bars and bars.classPowerShape)
  if classShape == "CIRCLE" then return "ROUND" end
  if classShape == "DIAMOND" or classShape == "HEX" then return "CRYSTAL" end
  return "BAR"
end

local function FontFromGlobal()
  local fn = _G.MSUF_GetFontPath
  if type(fn) == "function" then
    return fn() or DEFAULT_FONT
  end
  return DEFAULT_FONT
end

local function FontFlagsFromGlobal()
  local fn = _G.MSUF_GetFontFlags
  if type(fn) == "function" then
    return fn() or "OUTLINE"
  end
  return "OUTLINE"
end

local function ComposeFontFlags(outline, monochrome)
  local flags = ""
  outline = tostring(outline or "OUTLINE"):upper()
  if outline == "THICKOUTLINE" then
    flags = "THICKOUTLINE"
  elseif outline ~= "NONE" and outline ~= "" then
    flags = "OUTLINE"
  end
  if monochrome == true then
    flags = flags ~= "" and (flags .. ",MONOCHROME") or "MONOCHROME"
  end
  return flags
end

local function ResolveFontFlags(general, conf)
  local outline = "OUTLINE"
  local monochrome = general and general.fontMonochrome == true
  if general and general.noOutline then
    outline = "NONE"
  elseif general and general.boldText then
    outline = "THICKOUTLINE"
  end
  if conf and conf.fontOverride == true then
    if conf.noOutline ~= nil or conf.boldText ~= nil then
      if conf.noOutline then outline = "NONE"
      elseif conf.boldText then outline = "THICKOUTLINE"
      else outline = "OUTLINE" end
    end
    if conf.fontMonochrome ~= nil then
      monochrome = conf.fontMonochrome == true
    end
  end
  if not (conf and conf.fontOverride == true)
    and not (general and (general.noOutline or general.boldText or general.fontMonochrome)) then
    return FontFlagsFromGlobal()
  end
  return ComposeFontFlags(outline, monochrome)
end

local function ResolveFontTextAlpha(general, conf)
  local value = general and general.fontTextAlpha
  if conf and conf.fontOverride == true and conf.fontTextAlpha ~= nil then
    value = conf.fontTextAlpha
  end
  value = tonumber(value) or 1
  if value < 0.7 then return 0.7 end
  if value > 1 then return 1 end
  return value
end

local function ResolveFontBaselineOffset(general, conf)
  local value = general and general.fontBaselineOffset
  if conf and conf.fontOverride == true and conf.fontBaselineOffset ~= nil then
    value = conf.fontBaselineOffset
  end
  value = tonumber(value) or 0
  if value < -4 then return -4 end
  if value > 4 then return 4 end
  return value
end

local function ResolveFontShadow(general, conf)
  local enabled = not (general and general.textBackdrop == false)
  local strength = general and general.fontShadowStrength or "NORMAL"
  if conf and conf.fontOverride == true then
    if conf.textBackdrop ~= nil then enabled = conf.textBackdrop == true end
    if conf.fontShadowStrength ~= nil then strength = conf.fontShadowStrength end
  end
  strength = tostring(strength or "NORMAL"):upper()
  if strength == "SOFT" then return enabled, 0.55, 1, -1 end
  if strength == "DEEP" then return enabled, 1, 2, -2 end
  return enabled, 1, 1, -1
end

local function BossLayoutDelta(conf, index, def)
  conf = conf or {}
  def = def or DEFAULTS.boss
  index = Number(index, 1)
  local step = index - 1
  if step <= 0 then
    return 0, 0
  end
  local spacing = Number(conf.spacing, -42)
  local mode = conf.bossLayoutMode or "VERTICAL_DOWN"
  if mode == "VERTICAL_UP" then
    return 0, step * -spacing
  elseif mode == "HORIZONTAL_RIGHT" or mode == "HORIZONTAL_LEFT" then
    local width = Number(conf.width or conf.frameWidth, def.width)
    local height = Number(conf.height or conf.frameHeight, def.height)
    local visualGap = abs(spacing) - height
    local delta = step * (width + max(0, visualGap))
    if mode == "HORIZONTAL_LEFT" then
      delta = -delta
    end
    return delta, 0
  end
  return 0, step * spacing
end

local function BossOffset(conf, index, def)
  local x = Number(conf.offsetX or conf.x, def.x)
  local y = Number(conf.offsetY or conf.y, def.y)
  local dx, dy = BossLayoutDelta(conf, index, def)
  return x + dx, y + dy
end

local function PowerEnabled(unit, key, conf, bars)
  if conf.showPowerBar ~= nil then
    return conf.showPowerBar ~= false
  end
  local powerKey = POWER_KEYS[key]
  if powerKey and bars and bars[powerKey] ~= nil then
    return bars[powerKey] ~= false
  end
  if conf.showPowerText == nil and conf.showPower ~= nil then
    return conf.showPower ~= false
  end
  return DEFAULTS[key] and DEFAULTS[key].showPower ~= false
end

local function CastbarEnabled(unit, key, general)
  local shouldUseGlobal = _G.MSUF_ShouldUseMSUFCastbar
  if type(shouldUseGlobal) == "function" then
    return shouldUseGlobal(unit, general) == true
  end
  local shouldUse = UF.ShouldUseMSUFCastbar
  if type(shouldUse) == "function" then
    return shouldUse(unit) == true
  end
  local castbarKey = CASTBAR_KEYS[key]
  if not castbarKey then
    return false
  end
  return not (general and general[castbarKey] == false)
end

local function ResolveAnchorSettings(conf, general)
  local anchorFrameName = conf and conf.anchorFrameName
  if type(anchorFrameName) == "string" and anchorFrameName ~= "" then
    return anchorFrameName, conf.anchorToUnitframe, IsCooldownViewerFrameName(anchorFrameName)
  end

  local anchorToUnitframe = conf and conf.anchorToUnitframe
  if type(anchorToUnitframe) == "string"
    and anchorToUnitframe ~= ""
    and anchorToUnitframe ~= "GLOBAL"
    and anchorToUnitframe ~= "global"
    and anchorToUnitframe ~= "FREE" then
    if IsCooldownViewerFrameName(anchorToUnitframe) then
      return anchorToUnitframe, "GLOBAL", true
    end
    return nil, anchorToUnitframe, false
  end

  if general and general.anchorToCooldown == true then
    return "EssentialCooldownViewer", "GLOBAL", true
  end

  local globalAnchor = general and general.anchorName
  if IsCooldownViewerFrameName(globalAnchor) then
    return globalAnchor, "GLOBAL", true
  end
  if type(globalAnchor) == "string"
    and globalAnchor ~= ""
    and globalAnchor ~= "UIParent"
    and globalAnchor ~= "WorldFrame"
    and globalAnchor ~= "EssentialCooldownViewer" then
    return globalAnchor, "GLOBAL", false
  end

  return nil, anchorToUnitframe, false
end

local function CompileUnitPortrait(out, conf, general)
  out.portrait = out.portrait or {}
  local portraitMode = NormalizePortraitMode(conf)
  local portraitOverride = Number(conf.portraitSizeOverride, Number(conf.portraitSize, 0))
  local portraitAutoSize = max(16, Number(out.height, 30) - 4)
  local portraitSize = portraitOverride > 0 and max(16, portraitOverride) or portraitAutoSize
  out.portrait.enabled = portraitMode ~= "OFF"
  out.portrait.side = portraitMode == "RIGHT" and "RIGHT" or "LEFT"
  out.portrait.render = NormalizePortraitRender(conf.portraitRender)
  out.portrait.classStyle = NormalizePortraitClassStyle(conf.portraitClassStyle)
  out.portrait.shape = NormalizePortraitShape(conf.portraitShape)
  out.portrait.size = portraitSize
  out.portrait.x = Number(conf.portraitOffsetX, 0)
  out.portrait.y = Number(conf.portraitOffsetY, 0)
  CompilePortraitTexCoords(out.portrait, NormalizePortraitZoom(conf.portraitZoom))
  out.portrait.border = out.portrait.border or {}
  out.portrait.border.style = NormalizePortraitBorder(conf.portraitBorderStyle)
  out.portrait.border.thickness = max(1, Number(conf.portraitBorderThickness, 2))
  out.portrait.border.fill = conf.portraitFillBorder == true
  out.portrait.border.r = Number(general.portraitBorderColorR, 1)
  out.portrait.border.g = Number(general.portraitBorderColorG, 1)
  out.portrait.border.b = Number(general.portraitBorderColorB, 1)
  out.portrait.border.a = Number(general.portraitBorderColorA, 1)
  out.portrait.bg = out.portrait.bg or {}
  out.portrait.bg.enabled = conf.portraitBgEnabled == true
  out.portrait.bg.r = Number(general.portraitBgColorR, 0.05)
  out.portrait.bg.g = Number(general.portraitBgColorG, 0.05)
  out.portrait.bg.b = Number(general.portraitBgColorB, 0.05)
  out.portrait.bg.a = Number(general.portraitBgColorA, 0.85)
end

local function CompileUnitStatus(out, conf, general, key)
  out.status = out.status or {}
  local status = out.status
  status.key = key
  local statusAlpha = StatusNumber(conf, general, "stateIconsAlpha", 1, "statusIconsAlpha")
  if statusAlpha > 1 then
    statusAlpha = statusAlpha / 100
  end
  status.alpha = Clamp01(statusAlpha, 1)
  status.testMode = StatusBool(conf, general, "stateIconsTestMode", false, "statusIconsTestMode")
  status.useMidnight = StatusBool(conf, general, "statusIconsUseMidnightStyle", false)

  local levelSize = Number(conf.nameFontSize or general.nameFontSize, out.nameFontSize)
  local raidGroupSize = out.nameFontSize
  local statusTextSize = out.nameFontSize + 2
  for i = 1, #UNIT_STATUS_ENTRY_DEFS do
    local def = UNIT_STATUS_ENTRY_DEFS[i]
    local id = def[1]
    local fallbackSize = statusTextSize
    if id == "level" then
      fallbackSize = levelSize
    elseif id == "raidGroup" then
      fallbackSize = raidGroupSize
    end
    CompileStatusEntryDef(status, conf, general, key, def, fallbackSize)
  end

  local statusText = status.statusText
  local statusIndicators = type(general.statusIndicators) == "table" and general.statusIndicators or nil
  statusText.showAFK = statusIndicators ~= nil and statusIndicators.showAFK == true
  statusText.showDND = statusIndicators ~= nil and statusIndicators.showDND == true
  statusText.showDead = statusIndicators == nil or statusIndicators.showDead ~= false
  statusText.showGhost = statusIndicators == nil or statusIndicators.showGhost ~= false

  local pvp = status.pvp
  if pvp.enabled and UF.PVPIndicatorContextActive and not UF.PVPIndicatorContextActive() then
    pvp.enabled = false
    pvp.contextDisabled = true
  end

  local statusEnabled = false
  for i = 1, #UNIT_STATUS_ENTRY_DEFS do
    local entry = status[UNIT_STATUS_ENTRY_DEFS[i][1]]
    statusEnabled = statusEnabled or (entry and entry.enabled == true)
  end
  status.enabled = statusEnabled
end

local function ResolveUnitContext(db, unit)
  local key = UF.ConfigKeyForUnit(unit)
  local def = DEFAULTS[key] or DEFAULTS.player
  local conf = type(db[key]) == "table" and db[key] or {}
  if key == "targettarget" and type(db.targettarget) ~= "table" and type(db.tot) == "table" then
    conf = db.tot
  end
  local general = type(db.general) == "table" and db.general or {}
  local bars = type(db.bars) == "table" and db.bars or {}
  local bossIndex = unit and unit:match("^boss(%d+)$")
  return key, def, conf, general, bars, bossIndex
end

local function CompileUnitBase(out, unit, key, def, conf, general, bars, bossIndex)
  out.unit = unit
  out.key = key
  out.enabled = conf.enabled ~= false
  out.width = Number(conf.width or conf.frameWidth, def.width)
  out.height = Number(conf.height or conf.frameHeight, def.height)
  local globalCooldownAnchor
  out.anchorFrameName, out.anchorToUnitframe, globalCooldownAnchor = ResolveAnchorSettings(conf, general)
  local ecvRule = globalCooldownAnchor and ECV_ANCHORS[key] or nil
  if ecvRule then
    out.point = ecvRule[1]
    out.relativePoint = ecvRule[2]
  else
    out.point = conf.point or "CENTER"
    out.relativePoint = conf.relativePoint or out.point
  end
  local x, y
  if bossIndex then
    x, y = BossOffset(conf, tonumber(bossIndex) or 1, def)
  else
    x = Number(conf.offsetX or conf.x, def.x)
    y = Number(conf.offsetY or conf.y, def.y)
  end
  if ecvRule then
    x = x + Number(ecvRule[3], 0)
    y = y + Number(ecvRule[4], 0)
  end
  out.x, out.y = x, y

  out.texture = TextureFromScope(conf, general)
  out.backgroundTexture = BackgroundTextureFromScope(conf, general, out.texture)
  out.backgroundAlpha = ResolveBgAlpha(general, bars, conf)
  if conf.showName ~= nil then
    out.showName = conf.showName == true
  else
    out.showName = def.showName ~= false
  end
  out.showHealthText = conf.showHP ~= false and conf.showHPText ~= false
  if conf.showPowerText ~= nil then
    out.showPowerText = conf.showPowerText ~= false
  elseif conf.showPower ~= nil then
    out.showPowerText = conf.showPower ~= false
  else
    out.showPowerText = true
  end
  out.font = FontFromGlobal()
  out.fontFlags = ResolveFontFlags(general, conf)
  out.fontSize = Number(conf.fontSize or general.fontSize, 12)
  out.nameFontSize = Number(conf.nameFontSize or general.nameFontSize, out.fontSize)
  out.healthFontSize = Number(conf.hpFontSize or general.hpFontSize, out.fontSize)
  out.powerFontSize = Number(conf.powerFontSize or general.powerFontSize, out.fontSize)
  out.textColor = out.textColor or {}
  ResolveTextColor(general, out.textColor)
  out.textColor.a = ResolveFontTextAlpha(general, conf)
  do
    local shadowEnabled, shadowAlpha, shadowX, shadowY = ResolveFontShadow(general, conf)
    out.fontShadow = shadowEnabled
    out.fontShadowAlpha = shadowAlpha
    out.fontShadowX = shadowX
    out.fontShadowY = shadowY
  end
end

local function CompileUnitText(out, db, unit, key, conf, general, bars)
  local text = out.text or {}
  out.text = text
  text.healthLeft = NormalizeHealthTextMode(conf.textLeft, "NONE")
  text.healthCenter = NormalizeHealthTextMode(conf.textCenter, "NONE")
  text.healthRight = NormalizeHealthTextMode(conf.textRight or conf.hpTextMode or general.hpTextMode, "CURPERCENT")
  text.healthLeftHidePercentSymbol = ResolveTextSlotHidePercentSymbol(conf, general, "hpTextLeftHidePercentSymbol")
  text.healthCenterHidePercentSymbol = ResolveTextSlotHidePercentSymbol(conf, general, "hpTextCenterHidePercentSymbol")
  text.healthRightHidePercentSymbol = ResolveTextSlotHidePercentSymbol(conf, general, "hpTextRightHidePercentSymbol")
  if conf.hpTextReverse ~= nil then
    text.healthReverse = conf.hpTextReverse == true
  else
    text.healthReverse = general.hpTextReverse == true
  end
  text.healthDelimiter = conf.hpTextSeparator or general.hpTextSeparator or " - "
  text.healthPercentDecimals = (conf.healthTextDecimals == true or conf.hpTextDecimals == true) and 1 or 0
  text.nameAnchor = conf.nameTextAnchor or conf.nameAnchor or general.nameTextAnchor or general.nameAnchor or "LEFT"
  text.nameX = Number(conf.nameOffsetX or conf.nameTextOffsetX or general.nameOffsetX or general.nameTextOffsetX, 4)
  local fontBaselineOffset = ResolveFontBaselineOffset(general, conf)
  text.nameY = Number(conf.nameOffsetY or conf.nameTextOffsetY or general.nameOffsetY or general.nameTextOffsetY, -4) + fontBaselineOffset
  text.healthX = Number(conf.hpOffsetX or conf.hpTextOffsetX or general.hpOffsetX or general.hpTextOffsetX, -4)
  text.healthY = Number(conf.hpOffsetY or conf.hpTextOffsetY or general.hpOffsetY or general.hpTextOffsetY, -4) + fontBaselineOffset
  ApplySideTextOffsets(text, "health", "hpText", "hp", text.healthX, text.healthY, conf, general)
  text.powerLeft = NormalizePowerTextMode(conf.powerTextLeft, "NONE")
  text.powerCenter = NormalizePowerTextMode(conf.powerTextCenter, "NONE")
  text.powerRight = NormalizePowerTextMode(conf.powerTextRight or conf.powerTextMode or general.powerTextMode, "CURPERCENT")
  text.powerLeftHidePercentSymbol = ResolveTextSlotHidePercentSymbol(conf, general, "powerTextLeftHidePercentSymbol")
  text.powerCenterHidePercentSymbol = ResolveTextSlotHidePercentSymbol(conf, general, "powerTextCenterHidePercentSymbol")
  text.powerRightHidePercentSymbol = ResolveTextSlotHidePercentSymbol(conf, general, "powerTextRightHidePercentSymbol")
  text.powerDelimiter = conf.powerTextSeparator or general.powerTextSeparator or text.healthDelimiter
  text.powerX = Number(conf.powerOffsetX or conf.powerTextOffsetX or general.powerOffsetX or general.powerTextOffsetX, -4)
  text.powerY = Number(conf.powerOffsetY or conf.powerTextOffsetY or general.powerOffsetY or general.powerTextOffsetY, 4) + fontBaselineOffset
  ApplySideTextOffsets(text, "power", "powerText", "power", text.powerX, text.powerY, conf, general)
  text.nameLayer = Number(conf.nameTextLayer or general.nameTextLayer, 5)
  text.healthLayer = Number(conf.hpTextLayer or conf.textLayer or general.hpTextLayer or general.textLayer, 5)
  text.powerLayer = Number(conf.powerTextLayer or general.powerTextLayer, 2)
  ResolveNameShortening(db, general, conf, unit, text)
  text.nameClassColor, text.nameNpcColor, text.nameNpcClassColor = ResolveNameColorFlags(general, conf)
  ApplyNpcTypeFlags(text, general, "npcTypeColorText")
  ResolveToTInline(db, general, unit, text)
  text.healthColorByHealth = ResolveHealthTextColorByHealth(general, conf)
  text.powerColorByType = ResolvePowerTextColorByType(general, conf)
  text.directLayout = conf.directTextLayout == true
  if text.directLayout == true then
    CopyDirectTextLayout(text, conf)
    local npcTypeNameColor = NPCTypeTextColorEnabled(text)
    if text.nameClassColor ~= true and text.nameNpcColor ~= true and text.nameNpcClassColor ~= true and not npcTypeNameColor then
      text.nameColor = text.directNameColor
    else
      text.nameColor = nil
    end
  else
    text.nameColor = nil
    ClearDirectTextLayout(text)
  end
  text.shortNumbers = general.useShortNumbers ~= false
  text.hidePercentSymbol = general.hidePercentSymbol == true
end

local function CompileUnitHealth(out, conf, general, bars)
  local health = out.health or {}
  out.health = health
  health.texture = out.texture
  health.backgroundTexture = out.backgroundTexture
  health.reverse = conf.reverseFillBars == true
  health.smooth = conf.smoothFill ~= false
  health.mode = ResolveUnitBarMode(conf, general)
  health.gradient = general.enableHealthGradient ~= false
  health.gradientLowR = Number(general.healthGradientLowR, 1)
  health.gradientLowG = Number(general.healthGradientLowG, 0)
  health.gradientLowB = Number(general.healthGradientLowB, 0)
  health.gradientMidR = Number(general.healthGradientMidR, 1)
  health.gradientMidG = Number(general.healthGradientMidG, 1)
  health.gradientMidB = Number(general.healthGradientMidB, 0)
  health.gradientHighR = Number(general.healthGradientHighR, 0)
  health.gradientHighG = Number(general.healthGradientHighG, 1)
  health.gradientHighB = Number(general.healthGradientHighB, 0)
  health.barGradient = ResolveBarGradient(conf, general, "enableGradient")
  ApplyNpcTypeFlags(health, general, "npcTypeColorBar")
  health.petColorEnabled = general.petFrameColorEnabled == true
  health.petR = Number(general.petFrameColorR, 0)
  health.petG = Number(general.petFrameColorG, 0.8)
  health.petB = Number(general.petFrameColorB, 0)
  if health.mode == "unified" then
    CopyColor(health, general.unifiedBarR or 0.1, general.unifiedBarG or 0.6, general.unifiedBarB or 0.9, 1)
  elseif health.mode == "dark" then
    ResolveDarkColor(general, health)
  else
    CopyColor(health, general.unifiedBarR or 0.1, general.unifiedBarG or 0.6, general.unifiedBarB or 0.9, 1)
  end
  health.background = ResolveHealthBackground(general, bars, health, health.background or {}, conf)
  health.backgroundMatchHealth = general.barBgMatchHPColor == true
  return health
end

local function CompileUnitPower(out, unit, key, conf, general, bars, health)
  local power = out.power or {}
  out.power = power
  power.enabled = PowerEnabled(unit, key, conf, bars)
  power.height = Number(conf.powerBarHeight or bars.powerBarHeight, 3)
  power.texture = out.texture
  power.backgroundTexture = out.backgroundTexture
  power.frequent = unit == "player" and bars.realtimePowerText == true
  power.alpha = Clamp01(conf.powerBarAlpha, 1)
  power.mode = ResolvePowerMode(general)
  power.colors = power.colors or {}
  wipe(power.colors)
  CopyPowerColorOverrides(power.colors, general.powerColorOverrides)
  CopyPowerColorOverrides(power.colors, general.classPowerColorOverrides, true)
  if conf.embedPowerBarIntoHealth ~= nil then
    power.embed = conf.embedPowerBarIntoHealth == true
  elseif bars.embedPowerBarIntoHealth ~= nil then
    power.embed = bars.embedPowerBarIntoHealth == true
  else
    power.embed = true
  end
  power.detached = conf.powerBarDetached == true
  if key == "player" and power.detached == true then
    power.texture = DetachedPowerTextureFromBars(bars, out.texture)
    power.backgroundTexture = DetachedPowerBackgroundTextureFromBars(bars, power.texture, out.backgroundTexture)
  end
  power.detachedWidth = Number(conf.detachedPowerBarWidth, out.width)
  power.detachedHeight = Number(conf.detachedPowerBarHeight, power.height)
  power.orbSize = Number(conf.detachedPowerOrbSize, 54)
  if power.orbSize < 20 then
    power.orbSize = 20
  elseif power.orbSize > 160 then
    power.orbSize = 160
  end
  power.detachedX = Number(conf.detachedPowerBarOffsetX, 0)
  power.detachedY = Number(conf.detachedPowerBarOffsetY, -4)
  power.detachedLevel = Number(conf.detachedPowerBarFrameLevelOffset, 6)
  power.textOnDetached = conf.detachedPowerBarTextOnBar == true
  power.detachedSyncClass = key == "player" and conf.detachedPowerBarSyncClassPower ~= false
  power.detachedAnchorClass = key == "player" and conf.detachedPowerBarAnchorToClassPower == true
  power.detachedClassWidth = ClassPowerFallbackWidth(out, bars)
  power.detachedClassWidthFrameName = CooldownWidthFrameName(bars.classPowerWidthMode)
  power.detachedWidthFrameName = CooldownWidthFrameName(bars.detachedPowerBarWidthMode)
  power.shape = (key == "player" and power.detached == true) and ResolveDetachedPowerShape(conf, bars) or "BAR"
  power.borderEnabled = Bool(conf.powerBarBorderEnabled, bars.powerBarBorderEnabled == true)
  power.borderThickness = Number(conf.powerBarBorderThickness or bars.powerBarBorderThickness or bars.powerBarBorderSize, 1)
  if power.borderThickness < 0 then
    power.borderThickness = 0
  elseif power.borderThickness > 10 then
    power.borderThickness = 10
  end
  power.detachedOutline = Number(bars.detachedPowerBarOutline, power.borderThickness)
  if power.detachedOutline < 0 then
    power.detachedOutline = 0
  elseif power.detachedOutline > 8 then
    power.detachedOutline = 8
  end
  power.borderR = Number(ScopedValue(conf, general, "barOutlineColorR", general.barBorderR), 0)
  power.borderG = Number(ScopedValue(conf, general, "barOutlineColorG", general.barBorderG), 0)
  power.borderB = Number(ScopedValue(conf, general, "barOutlineColorB", general.barBorderB), 0)
  power.borderA = Number(ScopedValue(conf, general, "barOutlineColorA", general.barBorderA), 1)
  if power.mode == "unified" then
    CopyColor(power, general.unifiedBarR or 0.1, general.unifiedBarG or 0.6, general.unifiedBarB or 0.9, 1)
  elseif power.mode == "dark" then
    ResolveDarkColor(general, power)
  elseif power.mode == "static" then
    CopyColor(power, general.powerBarColorR or 0.1, general.powerBarColorG or 0.35, general.powerBarColorB or 0.95, 1)
  else
    CopyColor(power, 0.1, 0.35, 0.95, 1)
  end
  power.background = ResolvePowerBackground(general, bars, health, power.background or {}, conf)
  power.backgroundMatchHealth = general.powerBarBgMatchBarColor == true or bars.powerBarBgMatchBarColor == true
  power.barGradient = ResolveBarGradient(conf, general, "enablePowerGradient")
  power.reverse = health.reverse == true
  if conf.powerSmoothFill ~= nil then
    power.smooth = conf.powerSmoothFill == true
  else
    power.smooth = unit == "player" and bars.smoothPowerBar ~= false or false
  end
end

local function CompileUnitPrediction(out, conf, general, key)
  local pred = out.prediction or {}
  out.prediction = pred
  local absorbMode = Number(ScopedValue(conf, general, "absorbTextMode", nil), nil)
  if absorbMode then
    pred.absorb = absorbMode == 2 or absorbMode == 3
  else
    pred.absorb = ScopedValue(conf, general, "enableAbsorbBar", true) ~= false
  end
  pred.heal = general.showSelfHealPrediction == true or general.enableHealPrediction == true
  pred.healAbsorb = pred.absorb == true and ScopedValue(conf, general, "healAbsorbEnabled", true) ~= false
  pred.test = AbsorbTextureTestEnabledForScope(key)
  if pred.test == true then
    pred.absorb = true
    pred.heal = true
    pred.healAbsorb = true
  end
  pred.enabled = pred.heal == true or pred.absorb == true or pred.healAbsorb == true
  pred.texture = out.texture
  pred.healAnchorMode = Number(ScopedValue(conf, general, "healPredAnchorMode", 3), 3)
  pred.absorbAnchorMode = Number(ScopedValue(conf, general, "absorbAnchorMode", 2), 2)
  pred.overAbsorbOverlay = ScopedValue(conf, general, "overAbsorbOverlay", false) == true
  pred.absorbTexture = ScopedValue(conf, general, "absorbBarTexture", nil)
  pred.healAbsorbTexture = ScopedValue(conf, general, "healAbsorbBarTexture", nil)
  FillPredictionColors(pred, general, conf, ScopedValue, Number)
end

local function CompileUnitDispel(out, conf, general)
  local dispel = out.dispel or {}
  out.dispel = dispel
  dispel.r = 0.25
  dispel.g = 0.75
  dispel.b = 1
  dispel.a = 1
  local overlay = out.dispelOverlay or {}
  out.dispelOverlay = overlay
  overlay.enabled = ScopedValue(conf, general, "unitDispelOverlayEnabled", false) == true
  overlay.trigger = NormalizeDispelOverlayTrigger(ScopedValue(conf, general, "unitDispelOverlayTrigger", "BORDER"))
  overlay.style = NormalizeDispelOverlayStyle(ScopedValue(conf, general, "unitDispelOverlayStyle", "FULL"))
  overlay.onHealth = ScopedValue(conf, general, "unitDispelOverlayOnHealth", true) ~= false
  overlay.alpha = Clamp01(ScopedValue(conf, general, "unitDispelOverlayAlpha", 0.35), 0.35)
end

local function CompileUnitBorder(out, conf, general, bars)
  local border = out.border or {}
  out.border = border
  local outlineThickness = conf.hlOverride == true and conf.barOutlineThickness ~= nil and conf.barOutlineThickness or bars.barOutlineThickness
  if outlineThickness == nil then
    outlineThickness = general.useBarBorder == false and 0 or 1
  end
  border.thickness = Number(outlineThickness, 1)
  border.enabled = bars.showBarBorder ~= false and border.thickness > 0
  border.strata = NormalizeFrameOutlineStrata(conf.hlOverride == true and conf.barOutlineStrata ~= nil and conf.barOutlineStrata or bars.barOutlineStrata)
  border.r = Number(ScopedValue(conf, general, "barOutlineColorR", general.barBorderR), 0)
  border.g = Number(ScopedValue(conf, general, "barOutlineColorG", general.barBorderG), 0)
  border.b = Number(ScopedValue(conf, general, "barOutlineColorB", general.barBorderB), 0)
  border.a = Number(ScopedValue(conf, general, "barOutlineColorA", general.barBorderA), 1)
  border.highlightThickness = Number(ScopedValue(conf, general, "highlightBorderThickness", bars.highlightBorderThickness or general.highlightBorderThickness), border.thickness)
  border.aggroR = Number(general.hlAggroColorR or general.aggroBorderColorR or general.aggroBorderR, 1.00)
  border.aggroG = Number(general.hlAggroColorG or general.aggroBorderColorG or general.aggroBorderG, 0.55)
  border.aggroB = Number(general.hlAggroColorB or general.aggroBorderColorB or general.aggroBorderB, 0.00)
  border.purgeR = Number(general.hlPurgeColorR or general.purgeBorderColorR, 1.00)
  border.purgeG = Number(general.hlPurgeColorG or general.purgeBorderColorG, 0.85)
  border.purgeB = Number(general.hlPurgeColorB or general.purgeBorderColorB, 0.00)
  local bossColor = general.bossTargetHighlightColor
  border.bossTargetR = Number(type(bossColor) == "table" and bossColor[1], 1.00)
  border.bossTargetG = Number(type(bossColor) == "table" and bossColor[2], 0.82)
  border.bossTargetB = Number(type(bossColor) == "table" and bossColor[3], 0.00)
  border.prioEnabled, border.prioOrder = CompileBorderPriority(conf, general)
  local legacyDispelBorder = general.dispelBorderEnabled == true or general.hlDispelBorderEnabled == true
  if general.dispelBorderEnabled == nil and general.hlDispelBorderEnabled == nil then
    legacyDispelBorder = true
  end
  border.aggro = OutlineModeEnabled(ScopedValue(conf, general, "aggroOutlineMode", nil),
    general.aggroIndicatorMode == "border" or general.enableAggroHighlight == true)
  border.dispel = OutlineModeEnabled(ScopedValue(conf, general, "dispelOutlineMode", nil),
    legacyDispelBorder)
  border.dispelTrigger = NormalizeDispelDetectTrigger(ScopedValue(conf, general, "dispelBorderTrigger", "DISPEL_TYPE"))
  border.purge = OutlineModeEnabled(ScopedValue(conf, general, "purgeOutlineMode", nil),
    general.purgeBorderEnabled == true or general.hlPurgeBorderEnabled == true)
end

local function CompileUnitTail(out, unit, key, conf, general, bars)
  CompileUnitPortrait(out, conf, general)
  CompileUnitStatus(out, conf, general, key)

  out.auras = out.auras or {}
  local A3 = MSUF.MSUF_Auras3
  out.auras.enabled = (A3 and A3.UnitFrameAuraEnabled and A3.UnitFrameAuraEnabled(unit) == true)
    or (out.border and out.border.dispel == true)
    or (out.dispelOverlay and out.dispelOverlay.enabled == true)
    or false

  out.castbar = out.castbar or {}
  out.castbar.enabled = CastbarEnabled(unit, key, general)

  out.classPower = out.classPower or {}
  out.classPower.enabled = key == "player" and bars.showClassPower ~= false or false
end

--- ResolveUnit is the main DB-to-runtime-spec compiler for one unit token.
--- Keep it deterministic and side-effect-light: frame creation, region updates,
--- and event registration happen in Factory/Core after this spec exists.
local function ResolveUnit(db, unit, out)
  out = out or {}
  wipe(out)

  local key, def, conf, general, bars, bossIndex = ResolveUnitContext(db, unit)
  CompileUnitBase(out, unit, key, def, conf, general, bars, bossIndex)
  CompileUnitText(out, db, unit, key, conf, general, bars)
  local health = CompileUnitHealth(out, conf, general, bars)
  CompileUnitPower(out, unit, key, conf, general, bars, health)
  CompileUnitPrediction(out, conf, general, key)

  CompileAlpha(out, conf, general, key)
  CompileRange(out, conf, general, key)

  CompileLoadConditions(out, conf)
  CompileUnitDispel(out, conf, general)
  CompileUnitBorder(out, conf, general, bars)
  CompileUnitTail(out, unit, key, conf, general, bars)

  return out
end

Config.specs = Config.specs or {}

local function ConfigInCombat()
  return InCombatLockdown and InCombatLockdown()
end

function Config.BossLayoutDelta(conf, index)
  return BossLayoutDelta(conf, index, DEFAULTS.boss)
end

function Config.BossLayoutOffset(conf, index)
  return BossOffset(conf, index, DEFAULTS.boss)
end

--- Full refresh recompiles every managed unit spec. This is a cold/warm
--- operation for profile changes, option edits, and explicit rebuild requests;
--- do not call it from UNIT_HEALTH/POWER/AURA style gameplay events.
function Config.Refresh()
  if ConfigInCombat() then
    Config.dirty = true
    return Config.specs
  end
  local db = EnsureDB()
  for i = 1, #UF.unitOrder do
    local unit = UF.unitOrder[i]
    Config.specs[unit] = ResolveUnit(db, unit, Config.specs[unit])
  end
  Config.serial = (Config.serial or 0) + 1
  Config.dirty = nil
  return Config.specs
end

local function MSUF_GetBossLayoutDelta(index, conf)
  local db = EnsureDB()
  conf = conf or (db and db.boss) or {}
  return BossLayoutDelta(conf, index, DEFAULTS.boss)
end
ExportPublic("MSUF_GetBossLayoutDelta", MSUF_GetBossLayoutDelta)

function Config.RefreshUnit(unit)
  if not (unit and UF.IsManagedUnit and UF.IsManagedUnit(unit)) then
    return nil
  end
  if ConfigInCombat() then
    Config.dirty = true
    return Config.specs[unit]
  end
  local db = EnsureDB()
  Config.specs[unit] = ResolveUnit(db, unit, Config.specs[unit])
  Config.serial = (Config.serial or 0) + 1
  return Config.specs[unit]
end

function Config.GetSpec(unit)
  if Config.dirty == true and not ConfigInCombat() then
    Config.Refresh()
  end
  if not Config.specs[unit] then
    Config.Refresh()
  end
  return Config.specs[unit]
end

function Config.GetDB()
  return EnsureDB()
end

function Config.GetUnitDB(unit)
  local db = EnsureDB()
  return db[UF.ConfigKeyForUnit(unit)]
end

Config.settingsCache = Config.settingsCache or {}

--- Small read-mostly cache for runtime color/font decisions that are shared by
--- many frames. Hot code reads this cache instead of walking MSUF_DB.general.
local function BuildSettingsCache(db)
  local cache = Config.settingsCache
  local general = type(db.general) == "table" and db.general or {}
  local bars = type(db.bars) == "table" and db.bars or {}
  local dr, dg, dbb
  local dark = {}
  ResolveDarkColor(general, dark)
  dr, dg, dbb = dark.r, dark.g, dark.b
  local healthBg = ResolveHealthBackground(general, bars, nil, cache._healthBg or {})
  cache._healthBg = healthBg
  local powerBg = ResolvePowerBackground(general, bars, nil, cache._powerBg or {})
  cache._powerBg = powerBg

  cache.dbRef = db
  cache.generalRef = general
  cache.barsRef = bars
  cache.settingsSerial = Config.serial or 0
  cache.barMode = ResolveBarMode(general)
  cache.darkMode = general.darkMode == true
  cache.darkBgBrightness = Clamp01(general.darkBgBrightness, 0.25)
  cache.darkBarR, cache.darkBarG, cache.darkBarB = dr, dg, dbb
  cache.unifiedBarR = Number(general.unifiedBarR, 0.1)
  cache.unifiedBarG = Number(general.unifiedBarG, 0.6)
  cache.unifiedBarB = Number(general.unifiedBarB, 0.9)
  cache.healthGradientEnabled = general.enableHealthGradient ~= false
  cache.healthGradientLowR = Number(general.healthGradientLowR, 1)
  cache.healthGradientLowG = Number(general.healthGradientLowG, 0)
  cache.healthGradientLowB = Number(general.healthGradientLowB, 0)
  cache.healthGradientMidR = Number(general.healthGradientMidR, 1)
  cache.healthGradientMidG = Number(general.healthGradientMidG, 1)
  cache.healthGradientMidB = Number(general.healthGradientMidB, 0)
  cache.healthGradientHighR = Number(general.healthGradientHighR, 0)
  cache.healthGradientHighG = Number(general.healthGradientHighG, 1)
  cache.healthGradientHighB = Number(general.healthGradientHighB, 0)
  cache.barBackgroundAlpha = ResolveBgAlpha(general, bars)
  cache.barBgTintR, cache.barBgTintG, cache.barBgTintB, cache.barBgTintA = healthBg.r, healthBg.g, healthBg.b, healthBg.a
  cache.powerBgTintR, cache.powerBgTintG, cache.powerBgTintB, cache.powerBgTintA = powerBg.r, powerBg.g, powerBg.b, powerBg.a
  cache.barBgClassColor = general.barBgClassColor == true
  cache.barBgMatchHPColor = general.barBgMatchHPColor == true
  cache.powerBarBgMatchHPColor = general.powerBarBgMatchBarColor == true or bars.powerBarBgMatchBarColor == true
  cache.petFrameColorEnabled = general.petFrameColorEnabled == true
  cache.petFrameColorR = Number(general.petFrameColorR, 0)
  cache.petFrameColorG = Number(general.petFrameColorG, 0.8)
  cache.petFrameColorB = Number(general.petFrameColorB, 0)
  ApplyNpcTypeFlags(cache, general, "npcTypeColorBar")
  ApplyNpcTypeFlags(cache, general, "npcTypeColorText")
  cache.classColors = cache.classColors or {}
  local classColors = type(db.classColors) == "table" and db.classColors or nil
  local palette = MSUF.MSUF_FONT_COLORS or _G.MSUF_FONT_COLORS
  for i = 1, #CLASS_TOKENS do
    local token = CLASS_TOKENS[i]
    local dst = cache.classColors[token] or {}
    cache.classColors[token] = dst
    local src = classColors and classColors[token]
    if type(src) == "table" and tonumber(src.r or src[1]) and tonumber(src.g or src[2]) and tonumber(src.b or src[3]) then
      dst.r, dst.g, dst.b = Number(src.r or src[1], 1), Number(src.g or src[2], 1), Number(src.b or src[3], 1)
    elseif type(src) == "string" and palette and palette[src] then
      local c = palette[src]
      dst.r, dst.g, dst.b = Number(c.r or c[1], 1), Number(c.g or c[2], 1), Number(c.b or c[3], 1)
    else
      local c = _G.RAID_CLASS_COLORS and _G.RAID_CLASS_COLORS[token]
      dst.r, dst.g, dst.b = c and c.r or 0.12, c and c.g or 0.62, c and c.b or 0.95
    end
  end
  cache.npcColors = cache.npcColors or {}
  local npcColors = type(db.npcColors) == "table" and db.npcColors or nil
  for kind, fallback in pairs(NPC_COLOR_DEFAULTS) do
    local dst = cache.npcColors[kind] or {}
    cache.npcColors[kind] = dst
    local src = npcColors and npcColors[kind]
    if type(src) == "table" then
      dst.r = Number(src.r or src[1], fallback[1])
      dst.g = Number(src.g or src[2], fallback[2])
      dst.b = Number(src.b or src[3], fallback[3])
    else
      dst.r, dst.g, dst.b = fallback[1], fallback[2], fallback[3]
    end
  end
  Config.settingsCacheDirty = nil
  return cache
end

function Config.GetSettingsCache()
  local db = EnsureDB()
  local cache = Config.settingsCache
  if Config.dirty == true and ConfigInCombat() and cache.dbRef ~= nil then
    return cache
  end
  if Config.settingsCacheDirty == true then
    return BuildSettingsCache(db)
  end
  if cache.dbRef == db and cache.settingsSerial == (Config.serial or 0) then
    return cache
  end
  return BuildSettingsCache(db)
end

ExportPublic("MSUF_UFCore_GetSettingsCache", Config.GetSettingsCache)

function Config.RefreshSettingsCache()
  Config.settingsCacheDirty = true
  if ConfigInCombat() then
    Config.dirty = true
    return Config.settingsCache
  end
  return Config.GetSettingsCache()
end

ExportPublic("MSUF_UFCore_RefreshSettingsCache", Config.RefreshSettingsCache)

local function MSUF_UFCore_GetClassBarColorFast(classToken)
  local cache = Config.GetSettingsCache()
  local c = cache and cache.classColors and cache.classColors[classToken]
  if c then
    return c.r, c.g, c.b
  end
  c = classToken and _G.RAID_CLASS_COLORS and _G.RAID_CLASS_COLORS[classToken]
  if c then
    return c.r, c.g, c.b
  end
  return 0.12, 0.62, 0.95
end
ExportPublic("MSUF_UFCore_GetClassBarColorFast", MSUF_UFCore_GetClassBarColorFast)

local function MSUF_UFCore_GetNPCReactionColorFast(kind)
  local cache = Config.GetSettingsCache()
  local c = cache and cache.npcColors and cache.npcColors[kind]
  if c then
    return c.r, c.g, c.b
  end
  local fallback = NPC_COLOR_DEFAULTS[kind] or NPC_COLOR_DEFAULTS.enemy
  return fallback[1], fallback[2], fallback[3]
end
ExportPublic("MSUF_UFCore_GetNPCReactionColorFast", MSUF_UFCore_GetNPCReactionColorFast)
