-- LidCouncil_Core.lua
-- LidCouncil addon with in-game editor (Panels + Profiles + UI Settings), drag/resize, per-panel lock, profiles.

local function HasSavedData(value)
  return type(value) == "table" and next(value) ~= nil
end

if not HasSavedData(LidCouncilDB) and HasSavedData(LidLootDB) then
  LidCouncilDB = LidLootDB
end

LidCouncilDB = LidCouncilDB or {}
_G.LIDCOUNCIL_ADDON_NAME = "LidCouncil"

_G.LIDCOUNCIL_Debug = _G.LIDCOUNCIL_Debug or { info = {}, errors = {}, silent = {} }

local function GetDebugStore()
  return _G.LIDCOUNCIL_Debug
end

local function GetEditorFrame()
  local e = _G.LIDCOUNCIL_Editor
  if e and e.__lidCouncilEditor then return e end
  return nil
end

local function DebugInit()
  local debugStore = GetDebugStore()
  if not debugStore then
    _G.LIDCOUNCIL_Debug = { info = {}, errors = {}, silent = {} }
    debugStore = _G.LIDCOUNCIL_Debug
  end
  debugStore.info = debugStore.info or {}
  debugStore.errors = debugStore.errors or {}
  debugStore.silent = debugStore.silent or {}
end

-- Keep internal helpers local; expose only namespaced LIDCOUNCIL_* APIs.
local Print, RootDB, DB, DeepCopy
local GetSpecKey, GetCurrentSpecID, GetCharacterKey
local AddCharacterToList, GetCharacterList
local LoadLayoutFromSpec
local EnsureDefaults, RunRootMigrations
local EnsureLootProfileIdentity, AssignNewLootProfileIdentity
local EnsureLootProfileSyncState, MarkLootProfileAsIndependentClone
local EnsureLootProfileRevisionState, GetLootProfileDomainRevision, TouchLootProfileDomain
local GetLootProfileRevisionSummary
local BuildLootProfileSnapshotDiff
local BuildLootProfileDomainSnapshotDiff, BuildLootProfileSubdomainSnapshotDiff, BuildLootProfileSubdomainPatchDiff
local BuildLootProfileTransferConflictPreview, ApplyLootProfileTransferConflictResolution
local GetLootProfileSubchecksumManifest
local NormalizeLootProfileSubdomainList, CloneLootProfileSubdomainList, FilterLootProfilePatchCapableSubdomains
local GetLootProfileSubdomainKeysForDomain, GetLootProfileSubdomainLabel, GetLootProfileSubdomainParentDomain
local ResolveLootProfileDomainsForSubdomains
local GetLootProfile, GetLootProfileNames, SetCurrentLootProfile
local GetEnabledLootProfileName, GetPrimaryLootProfileName
local CreateLootProfile, DeleteLootProfile, CopyLootProfile, SetLootProfileEnabled
local GetLootProfileId, GetLootProfileIdentityInfo, FindLootProfileNameById
local GetLootProfileAccessInfoForRole, GetLootProfilePermissionPolicyRoleState, GetLootProfilePermissionPolicyDiagnostics, SetLootProfilePermissionPolicyValue, ResetLootProfilePermissionPolicy
local NormalizeLootProfileEnabledState, EnsureProfileRecord
local TouchLootProfile, EnsureLootLedgerMeta
local AddLootLedgerEntry, RemoveLootLedgerEntry, ResolveLootPointValue, ResolveLootAwardOutcome, AwardLootToCharacter, GetLootLedgerCharacters
local RecordLootRollStat, RecordLootVoteStat, GetLootRollAwards, ValidateSimulationVoteExclusion
local NormalizeLootProfileRole, RemoveLootProfilePermissionMember, SetLootProfilePermissionMember
local EnsureLootProfilePermissionState, GetLootProfileRoleLocal, IsLootProfileManagerRole
local CollectLootProfileKnownCharacters, GetLootProfileRole, GetLootProfileMaster, GetLootProfileRoleEntries
local GetLootProfileAccessInfo, EnsureLootProfileMemberRole, SyncLootProfilePermissionState
local SetLootProfileMemberRole, TransferLootProfileMaster
local GetSimulationRoleOverride, GetSimulationRoleMode, SetSimulationRoleOverride
local EnsureLootRootState, SetLootRootSessionState, FindActiveSessionByState
local CollectActiveLootSessions, NormalizeActiveLootSessions, FindLootSession
local StartLootSession, ActivateLootSession, GetLootSession, JoinLootSession, GetActiveLootSession
local StopLootSession, AddLootSessionMember, UpsertLootSessionMember, RemoveLootSessionMember
local FinishLootSession
local GetEnglishClassAndSpec
local characterListSnapshotSource, characterListSnapshot
local ApplyLootRollTemplate
local coreSystemHelpers
local CreateLootProfileAccessHelpers = rawget(_G, "LIDCOUNCIL_CreateLootProfileAccessHelpers_Impl")
if type(CreateLootProfileAccessHelpers) ~= "function" then
  error("LidCouncil missing loot profile access helpers.")
end
local CreateLootLedgerHelpers = rawget(_G, "LIDCOUNCIL_CreateLootLedgerHelpers_Impl")
if type(CreateLootLedgerHelpers) ~= "function" then
  error("LidCouncil missing loot ledger helpers.")
end
local CreateLootRollStatsHelpers = rawget(_G, "LIDCOUNCIL_CreateLootRollStatsHelpers_Impl")
if type(CreateLootRollStatsHelpers) ~= "function" then
  error("LidCouncil missing loot roll stats helpers.")
end
local CreateLootSessionHelpers = rawget(_G, "LIDCOUNCIL_CreateLootSessionHelpers_Impl")
if type(CreateLootSessionHelpers) ~= "function" then
  error("LidCouncil missing loot session helpers.")
end
local CreateLootProfileStateHelpers = rawget(_G, "LIDCOUNCIL_CreateLootProfileStateHelpers_Impl")
if type(CreateLootProfileStateHelpers) ~= "function" then
  error("LidCouncil missing loot profile state helpers.")
end
local CreateCoreSystemHelpers = rawget(_G, "LIDCOUNCIL_CreateCoreSystemHelpers_Impl")
if type(CreateCoreSystemHelpers) ~= "function" then
  error("LidCouncil missing core system helpers.")
end

local DEBUG_MAX_INFO = 200
local DEBUG_MAX_ERRORS = 200
local DEBUG_MAX_SILENT = 200

local function PushDebug(list, msg, maxItems)
  list[#list + 1] = msg
  local overflow = #list - maxItems
  if overflow > 0 then
    for _ = 1, overflow do
      table.remove(list, 1)
    end
  end
end

local function LogInfo(msg)
  DebugInit()
  if type(msg) ~= "string" then msg = tostring(msg) end
  local debugStore = GetDebugStore()
  PushDebug(debugStore.info, msg, DEBUG_MAX_INFO)
end

local function LogError(msg)
  DebugInit()
  if type(msg) ~= "string" then msg = tostring(msg) end
  local debugStore = GetDebugStore()
  PushDebug(debugStore.errors, msg, DEBUG_MAX_ERRORS)
  local e = GetEditorFrame()
  if e and e.RefreshAbout then e:RefreshAbout() end
end

local function LogSilent(msg)
  local ui = LidCouncilDB and LidCouncilDB.ui
  if not (ui and ui.developerMode == true) then
    return
  end
  DebugInit()
  if type(msg) ~= "string" then msg = tostring(msg) end
  local debugStore = GetDebugStore()
  PushDebug(debugStore.silent, msg, DEBUG_MAX_SILENT)
  local e = GetEditorFrame()
  if e and e.RefreshAbout then e:RefreshAbout() end
end

_G.LIDCOUNCIL_LogInfo = LogInfo
_G.LIDCOUNCIL_LogError = LogError
_G.LIDCOUNCIL_LogSilent = LogSilent

LogInfo("Core.lua loaded")

local function TouchLootProfileSettings(profile)
  if type(TouchLootProfileDomain) == "function" then
    return TouchLootProfileDomain(profile, "settings")
  end
end

local function TouchLootProfilePermissions(profile)
  if type(TouchLootProfileDomain) == "function" then
    return TouchLootProfileDomain(profile, "permissions")
  end
end

local function TouchLootProfileLoot(profile)
  if type(TouchLootProfileDomain) == "function" then
    return TouchLootProfileDomain(profile, "loot")
  end
end

local function TouchLootProfileSession(profile)
  if type(TouchLootProfileDomain) == "function" then
    return TouchLootProfileDomain(profile, "session")
  end
end

local function TouchLootProfileOperational(profile)
  if type(TouchLootProfileDomain) == "function" then
    return TouchLootProfileDomain(profile, "operational")
  end
end

function Print(msg)
  DEFAULT_CHAT_FRAME:AddMessage("|cff66ccff[LidCouncil]|r " .. tostring(msg))
end

-- ============================================================
-- ##--------------------------DB--------------------------------
-- ============================================================

function RootDB()
  LidCouncilDB = LidCouncilDB or {}
  LidCouncilDB.profiles = LidCouncilDB.profiles or {}
  LidCouncilDB.profileGroups = LidCouncilDB.profileGroups or {}
  LidCouncilDB.localProfileBackups = LidCouncilDB.localProfileBackups or {}
  LidCouncilDB.currentProfile = LidCouncilDB.currentProfile or "Default"

  LidCouncilDB.ui = LidCouncilDB.ui or {}
  if type(RunRootMigrations) == "function" then
    RunRootMigrations(LidCouncilDB)
  end
  LidCouncilDB.ui.skin = "LP"
  local editorSkin = type(LidCouncilDB.ui.editorSkin) == "string" and string.lower(LidCouncilDB.ui.editorSkin) or "lidcouncil"
  if editorSkin == "white_orange" then
    editorSkin = "lootpilot"
  elseif editorSkin ~= "lootpilot" then
    editorSkin = "lidcouncil"
  end
  LidCouncilDB.ui.editorSkin = editorSkin
  if LidCouncilDB.ui.disableTutorialImages == nil then
    LidCouncilDB.ui.disableTutorialImages = false
  end
  if LidCouncilDB.ui.developerMode == nil then
    LidCouncilDB.ui.developerMode = false
  end
  if LidCouncilDB.ui.showInternalProfileId == nil then
    LidCouncilDB.ui.showInternalProfileId = false
  end
  if LidCouncilDB.ui.showProfileSize == nil then
    LidCouncilDB.ui.showProfileSize = false
  end
  if LidCouncilDB.ui.profileSyncChunkSize == nil then
    LidCouncilDB.ui.profileSyncChunkSize = 120
  end
  if LidCouncilDB.ui.profileSyncDelaySeconds == nil then
    LidCouncilDB.ui.profileSyncDelaySeconds = 0.12
  end
  if LidCouncilDB.ui.tutorialMode == nil then
    LidCouncilDB.ui.tutorialMode = false
  end
  if LidCouncilDB.ui.checklistDev == nil then
    LidCouncilDB.ui.checklistDev = false
  end
  if LidCouncilDB.ui.changelogDev == nil then
    LidCouncilDB.ui.changelogDev = false
  end
  if LidCouncilDB.ui.importExportFeature == nil then
    LidCouncilDB.ui.importExportFeature = false
  end
  if LidCouncilDB.ui.showSimulationProfile == nil then
    LidCouncilDB.ui.showSimulationProfile = false
  end
  if LidCouncilDB.ui.developerMode ~= true then
    LidCouncilDB.ui.showInternalProfileId = false
    LidCouncilDB.ui.showProfileSize = false
    LidCouncilDB.ui.tutorialMode = false
    LidCouncilDB.ui.checklistDev = false
    LidCouncilDB.ui.changelogDev = false
    LidCouncilDB.ui.showSimulationProfile = false
  end
  if LidCouncilDB.ui.panelEditMode ~= "PREVIEW" then
    LidCouncilDB.ui.panelEditMode = "EDITOR"
  end
  -- Keep editor scale fixed to the 4K baseline; size adapts to screen via layout.
  LidCouncilDB.ui.editorScalePreset = "4K"
  LidCouncilDB.ui.editorScale = 1.0
  local defaultMinimapSize = 28
  if LidCouncilDB.ui.editorScalePreset == "4K" then defaultMinimapSize = 37 end
  LidCouncilDB.ui.minimap = LidCouncilDB.ui.minimap or { size = defaultMinimapSize, angle = 45, alpha = 1, onlyOnHover = false }
  if LidCouncilDB.ui.minimap.size == nil then LidCouncilDB.ui.minimap.size = defaultMinimapSize end
  if LidCouncilDB.ui.minimap.angle == nil then LidCouncilDB.ui.minimap.angle = 45 end
  if LidCouncilDB.ui.minimap.alpha == nil then LidCouncilDB.ui.minimap.alpha = 1 end
  if LidCouncilDB.ui.minimap.onlyOnHover == nil then LidCouncilDB.ui.minimap.onlyOnHover = false end

  LidCouncilDB.characters = LidCouncilDB.characters or {}

  return LidCouncilDB
end

function DB()
  local root = RootDB()
  local name = root.currentProfile

  root.profiles[name] = root.profiles[name] or {
    panels = {},
    positions = {},
    spec = { auto = false, layouts = {} },
    __seeded = false
  }

  local db = root.profiles[name]
  db.spec = db.spec or { auto = false, layouts = {} }
  db.spec.layouts = db.spec.layouts or {}
  if db.spec.auto == nil then db.spec.auto = false end
  db.profileSpec = db.profileSpec or { enabled = false, mode = "SPEC", specID = nil, character = nil }
  if db.profileSpec.enabled == nil then db.profileSpec.enabled = false end
  if db.profileSpec.mode ~= "CHARACTER" then db.profileSpec.mode = "SPEC" end

  return db
end

local DEFAULT_LOOT_ROLL_OPTIONS = {
  { key = "need", label = "Need", color = { 0.22, 0.88, 0.32 }, icon = "Interface\\Buttons\\UI-GroupLoot-Dice-Up", pointBehavior = "ITEMLEVEL", popupSection = "NEED" },
  { key = "greed", label = "Greed", color = { 0.95, 0.82, 0.22 }, icon = "Interface\\Buttons\\UI-GroupLoot-Coin-Up", pointBehavior = "ITEMLEVEL", popupSection = "GREEDPASS" },
  { key = "pass", label = "Pass", color = { 0.62, 0.62, 0.62 }, icon = "Interface\\Buttons\\UI-GroupLoot-Pass-Up", pointBehavior = "NONE", popupSection = "GREEDPASS" },
}
local DEFAULT_LOOT_ROLL_TEMPLATE = "DEFAULT_WOW"
local LIDCOUNCIL_LOOT_ROLL_TEMPLATE = "LIDCOUNCIL"
local CUSTOM_LOOT_ROLL_TEMPLATE = "CUSTOM"
local DEFAULT_POINT_MODE = "LIDTABLE"
local DEFAULT_LOOT_ROLL_POINT_BEHAVIOR = "ITEMLEVEL"
local DEFAULT_LIDTABLE_ROWS = {}
local DEFAULT_LIDTABLE_ITEM_LEVELS = {
  [1] = 292,
  [2] = 295,
  [3] = 298,
  [4] = 302,
  [5] = 305,
  [6] = 308,
  [7] = 311,
  [8] = 315,
  [9] = 318,
  [10] = 321,
  [11] = 324,
  [12] = 328,
  [13] = 332,
  [14] = 336,
  [15] = 344,
}
for i = 1, 15 do
  DEFAULT_LIDTABLE_ROWS[i] = {
    points = i,
    itemLevel = DEFAULT_LIDTABLE_ITEM_LEVELS[i],
    setPiecePoints = 5,
    offSpecPoints = 0,
    transmogPoints = 0,
    disenchantPoints = 0,
  }
end

local NormalizeLootRollOptions, GetLootRollTemplateOptions, DetectLootRollTemplate, NormalizeLootRollTemplate
local ExtractItemID, NormalizePointMode, EnsureLidTableRows

local function TrimText(value)
  value = tostring(value or "")
  value = value:gsub("^%s+", "")
  value = value:gsub("%s+$", "")
  return value
end

local function NormalizeRaidInstanceTuple(a, b, c)
  local instanceID, instanceName
  if type(a) == "number" then
    instanceID = a
    instanceName = b
  elseif type(b) == "number" then
    instanceName = a
    instanceID = b
  elseif type(c) == "number" then
    instanceName = b or a
    instanceID = c
  end
  instanceName = TrimText(instanceName or "")
  return tonumber(instanceID), instanceName
end

local function EnsureCoreEncounterJournalLoaded()
  if type(EncounterJournal_LoadUI) == "function" then
    pcall(EncounterJournal_LoadUI)
  end
  return type(EJ_GetCurrentTier) == "function"
    and type(EJ_SelectTier) == "function"
    and type(EJ_GetInstanceByIndex) == "function"
end

local function CaptureCurrentLootSeasonSnapshot()
  if not EnsureCoreEncounterJournalLoaded() then
    return nil
  end

  local currentTier = tonumber(EJ_GetCurrentTier())
  if not currentTier then
    return nil
  end

  local originalTier = currentTier
  local tierName = "Current Season"
  if type(EJ_GetTierInfo) == "function" then
    local ok, rawTierName = pcall(EJ_GetTierInfo, currentTier)
    if ok and TrimText(rawTierName) ~= "" then
      tierName = TrimText(rawTierName)
    end
  end

  local raids = {}
  pcall(EJ_SelectTier, currentTier)
  local instanceIndex = 1
  while true do
    local ok, r1, r2, r3 = pcall(EJ_GetInstanceByIndex, instanceIndex, true)
    if not ok or r1 == nil then
      break
    end
    local instanceID, instanceName = NormalizeRaidInstanceTuple(r1, r2, r3)
    if instanceID then
      raids[#raids + 1] = {
        instanceID = instanceID,
        name = instanceName ~= "" and instanceName or ("Raid " .. tostring(instanceID)),
        tier = currentTier,
        tierName = tierName,
      }
    end
    instanceIndex = instanceIndex + 1
  end

  if originalTier and type(EJ_SelectTier) == "function" then
    pcall(EJ_SelectTier, originalTier)
  end

  table.sort(raids, function(a, b)
    return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
  end)
  if #raids == 0 then
    return nil
  end

  return {
    version = 1,
    capturedAt = time and time() or 0,
    tier = currentTier,
    tierName = tierName,
    raids = raids,
  }
end

function ExtractItemID(value)
  if type(value) == "number" then
    return math.floor(value)
  end
  local text = TrimText(value)
  if text == "" then
    return nil
  end
  local direct = tonumber(text)
  if direct then
    return math.floor(direct)
  end
  local linkID = text:match("item:(%d+)")
  if linkID then
    return tonumber(linkID)
  end
  return nil
end

local function ResolveDefaultRollOptionIcon(key, label)
  local text = string.lower(TrimText(key ~= "" and key or label))
  if text == "need" then
    return "Interface\\Buttons\\UI-GroupLoot-Dice-Up"
  elseif text == "greed" then
    return "Interface\\Buttons\\UI-GroupLoot-Coin-Up"
  elseif text == "pass" then
    return "Interface\\Buttons\\UI-GroupLoot-Pass-Up"
  elseif text == "disenchant" then
    return "Interface\\Icons\\inv_enchant_disenchant"
  end
  return nil
end

local lootRollTemplateIconNameMap

local function GetLootRollTemplateIconNameMap()
  if type(lootRollTemplateIconNameMap) == "table" then
    return lootRollTemplateIconNameMap
  end

  local iconNames = rawget(_G, "LIDCOUNCIL_ICON_FILE_NAMES")
  local api = rawget(_G, "LIDCOUNCIL_API")
  if type(iconNames) ~= "table" and type(api) == "table" then
    iconNames = api.LIDCOUNCIL_ICON_FILE_NAMES
  end
  if type(iconNames) ~= "table" then
    return nil
  end

  local map = {}
  for fileId, iconName in pairs(iconNames) do
    local cleanName = string.lower(TrimText(iconName))
    if cleanName ~= "" and map[cleanName] == nil then
      map[cleanName] = tonumber(fileId) or fileId
    end
  end
  lootRollTemplateIconNameMap = map
  return lootRollTemplateIconNameMap
end

local function ResolveLootRollTemplateIcon(value)
  local text = TrimText(value)
  if text == "" then
    return nil
  end

  local lowered = string.lower(text)
  if lowered == "pass icon" then
    return "Interface\\Buttons\\UI-GroupLoot-Pass-Up"
  end

  local numeric = tonumber(text)
  if numeric then
    return numeric
  end
  if text:find("\\", 1, true) then
    return text
  end

  local iconMap = GetLootRollTemplateIconNameMap()
  local resolved = type(iconMap) == "table" and iconMap[lowered] or nil
  if resolved ~= nil then
    return resolved
  end

  return "Interface\\Icons\\" .. text
end

local function HexColorToRgb(hex)
  local clean = string.upper(TrimText(hex))
  if clean:match("^%x%x%x%x%x%x$") then
    return {
      tonumber(clean:sub(1, 2), 16) / 255,
      tonumber(clean:sub(3, 4), 16) / 255,
      tonumber(clean:sub(5, 6), 16) / 255,
    }
  end
  return { 0.82, 0.82, 0.86 }
end

local function NormalizeLootRollOptionKey(value, fallback)
  local text = TrimText(value ~= "" and value or fallback)
  text = string.lower(text)
  text = text:gsub("%s+", "_")
  text = text:gsub("[^%w_]", "")
  if text == "" then
    return "option"
  end
  return text
end

function NormalizePointMode(value)
  return DEFAULT_POINT_MODE
end

local function NormalizeLootRollPointBehavior(value, key, label)
  local text = string.upper(TrimText(value))
  if text == "ITEMLEVEL" or text == "BIS" or text == "OFFSPEC" or text == "TRANSMOG" or text == "DISENCHANT" or text == "NONE" then
    return text
  end

  local hint = string.lower(TrimText((key ~= "" and key) or label))
  hint = hint:gsub("[%s%-%_]+", "")
  if hint == "pass" or hint == "none" then
    return "NONE"
  end
  if hint == "bis" or hint == "bestinslot" then
    return "BIS"
  end
  if hint == "offspec" or hint == "os" then
    return "OFFSPEC"
  end
  if hint == "transmog" or hint == "mog" then
    return "TRANSMOG"
  end
  if hint == "disenchant" or hint == "de" then
    return "DISENCHANT"
  end
  return DEFAULT_LOOT_ROLL_POINT_BEHAVIOR
end

function NormalizeLootRollTemplate(value)
  local text = string.upper(TrimText(value))
  if text == LIDCOUNCIL_LOOT_ROLL_TEMPLATE then
    return LIDCOUNCIL_LOOT_ROLL_TEMPLATE
  end
  if text == DEFAULT_LOOT_ROLL_TEMPLATE or text == "DEFAULTWOW" or text == "WOW" or text == "DEFAULT" then
    return DEFAULT_LOOT_ROLL_TEMPLATE
  end
  if text == CUSTOM_LOOT_ROLL_TEMPLATE then
    return CUSTOM_LOOT_ROLL_TEMPLATE
  end
  return nil
end

local function CreateLidCouncilLootRollTemplateOptions()
  return {
    {
      key = "bis",
      label = "BiS",
      color = HexColorToRgb("FF841F"),
      icon = ResolveLootRollTemplateIcon("garrison_weaponupgrade"),
      pointBehavior = "BIS",
      popupSection = "NEED",
    },
    {
      key = "almost_bis",
      label = "Almost BiS",
      color = HexColorToRgb("C80DEC"),
      icon = ResolveLootRollTemplateIcon("garrison_purpleweaponupgrade"),
      pointBehavior = "ITEMLEVEL",
      popupSection = "NEED",
    },
    {
      key = "big_upgrade",
      label = "Big Upgrade",
      color = HexColorToRgb("3F63D5"),
      icon = ResolveLootRollTemplateIcon("garrison_blueweaponupgrade"),
      pointBehavior = "ITEMLEVEL",
      popupSection = "NEED",
    },
    {
      key = "small_upgrade",
      label = "Small Upgrade (+iLvL)",
      color = HexColorToRgb("75DB42"),
      icon = ResolveLootRollTemplateIcon("garrison_greenweaponupgrade"),
      pointBehavior = "ITEMLEVEL",
      popupSection = "NEED",
    },
    {
      key = "off_spec",
      label = "Off-Spec",
      color = HexColorToRgb("FBFFFF"),
      icon = ResolveLootRollTemplateIcon("inv_shield_04"),
      pointBehavior = "OFFSPEC",
      popupSection = "GREEDPASS",
    },
    {
      key = "transmog",
      label = "Transmog",
      color = HexColorToRgb("ABADB4"),
      icon = ResolveLootRollTemplateIcon("inv_chest_cloth_17"),
      pointBehavior = "TRANSMOG",
      popupSection = "GREEDPASS",
    },
    {
      key = "disenchant",
      label = "Disenchant",
      color = HexColorToRgb("737379"),
      icon = ResolveLootRollTemplateIcon("inv_enchant_disenchant"),
      pointBehavior = "DISENCHANT",
      popupSection = "GREEDPASS",
    },
    {
      key = "pass",
      label = "Pass",
      color = HexColorToRgb("545458"),
      icon = ResolveLootRollTemplateIcon("pass icon"),
      pointBehavior = "NONE",
      popupSection = "GREEDPASS",
    },
  }
end

local function GetLootRollPointCounterKey(pointBehavior)
  local resolved = NormalizeLootRollPointBehavior(pointBehavior)
  if resolved == "BIS" then
    return "bis"
  end
  if resolved == "OFFSPEC" then
    return "offSpec"
  end
  if resolved == "TRANSMOG" then
    return "transmog"
  end
  if resolved == "DISENCHANT" then
    return "disenchant"
  end
  return nil
end

local function NormalizeLootRollPopupSection(value, pointBehavior, key, label)
  local text = string.upper(TrimText(value))
  if text == "NEED" or text == "GREEDPASS" then
    return text
  end

  local resolvedBehavior = NormalizeLootRollPointBehavior(pointBehavior, key, label)
  if resolvedBehavior == "OFFSPEC" or resolvedBehavior == "TRANSMOG" or resolvedBehavior == "DISENCHANT" or resolvedBehavior == "NONE" then
    return "GREEDPASS"
  end

  local hint = string.lower(TrimText((key ~= "" and key) or label))
  hint = hint:gsub("[%s%-%_]+", "")
  if hint == "greed" or hint == "pass" or hint == "offspec" or hint == "transmog" or hint == "mog" or hint == "disenchant" or hint == "de" then
    return "GREEDPASS"
  end
  return "NEED"
end

function EnsureLidTableRows(settings)
  settings = type(settings) == "table" and settings or {}
  settings.lidTable = settings.lidTable or {}
  settings.lidTable.rows = settings.lidTable.rows or {}

  local rows = settings.lidTable.rows
  for i = 1, 15 do
    local hasExistingRow = type(rows[i]) == "table"
    local existing = hasExistingRow and rows[i] or {}
    local defaults = DEFAULT_LIDTABLE_ROWS[i]
    rows[i] = {
      points = tonumber(existing.points) or defaults.points,
      itemLevel = hasExistingRow and tonumber(existing.itemLevel) or defaults.itemLevel,
      setPiecePoints = tonumber(existing.setPiecePoints) or defaults.setPiecePoints,
      offSpecPoints = tonumber(existing.offSpecPoints) or defaults.offSpecPoints,
      transmogPoints = tonumber(existing.transmogPoints) or defaults.transmogPoints,
      disenchantPoints = tonumber(existing.disenchantPoints) or defaults.disenchantPoints,
    }
  end
  for i = #rows, 16, -1 do
    rows[i] = nil
  end
  return rows
end

local function NormalizeLootLedgerMainAltRole(role)
  local resolvedRole = string.upper(TrimText(role))
  if resolvedRole == "ALT" then
    return "ALT"
  end
  return "MAIN"
end

local ResetSimulationProfileContent
local RepairSimulationProfileOwnership

EnsureLootLedgerMeta = function(profile)
  profile = type(profile) == "table" and profile or {}
  profile.lootLedgerMeta = profile.lootLedgerMeta or {}
  for characterKey, meta in pairs(profile.lootLedgerMeta) do
    local resolvedKey = TrimText(characterKey)
    local entry = type(meta) == "table" and meta or {}
    entry.penaltyPoints = tonumber(entry.penaltyPoints) or 0
    entry.classFile = TrimText(entry.classFile)
    entry.className = TrimText(entry.className)
    entry.specName = TrimText(entry.specName)
    entry.specIcon = TrimText(entry.specIcon)
    entry.note = TrimText(entry.note)
    entry.mainCharacter = TrimText(entry.mainCharacter)
    entry.mainAltRole = NormalizeLootLedgerMainAltRole(entry.mainAltRole)
    if entry.mainAltRole == "MAIN" or string.lower(entry.mainCharacter) == string.lower(resolvedKey) then
      entry.mainCharacter = ""
    end
    entry.summaryWith = nil
    if resolvedKey ~= characterKey then
      profile.lootLedgerMeta[characterKey] = nil
      if resolvedKey ~= "" then
        profile.lootLedgerMeta[resolvedKey] = entry
      end
    else
      profile.lootLedgerMeta[characterKey] = entry
    end
  end
  return profile.lootLedgerMeta
end

local profileSyncSupportHelpers
do
  local createProfileSyncSupportHelpers = rawget(_G, "LIDCOUNCIL_CreateCoreProfileSyncSupportHelpers_Impl")
  if type(createProfileSyncSupportHelpers) ~= "function" then
    error("LidCouncil missing core profile sync support helpers.")
  end
  profileSyncSupportHelpers = createProfileSyncSupportHelpers({
    TrimText = TrimText,
    EnsureLootLedgerMeta = function(profile)
      return EnsureLootLedgerMeta(profile)
    end,
    NormalizeLootLedgerMainAltRole = NormalizeLootLedgerMainAltRole,
    TouchLootProfileSettings = function(profile)
      return TouchLootProfileSettings(profile)
    end,
    TouchLootProfilePermissions = function(profile)
      return TouchLootProfilePermissions(profile)
    end,
    TouchLootProfileOperational = function(profile)
      return TouchLootProfileOperational(profile)
    end,
    GetLootProfile = function(name)
      return GetLootProfile(name)
    end,
    GetLootProfileRole = function(name, actorKey)
      return GetLootProfileRole(name, actorKey)
    end,
    GetLootProfileAccessInfoForRole = function(name, role)
      if type(GetLootProfileAccessInfoForRole) == "function" then
        return GetLootProfileAccessInfoForRole(name, role)
      end
      return nil
    end,
    EnsureLootProfilePermissionState = function(profile)
      if type(EnsureLootProfilePermissionState) == "function" then
        return EnsureLootProfilePermissionState(profile)
      end
      return nil
    end,
    NormalizeLootRollTemplate = function(value)
      return NormalizeLootRollTemplate(value)
    end,
    GetLootRollTemplateOptions = function(value)
      return GetLootRollTemplateOptions(value)
    end,
    NormalizeLootRollOptions = function(options)
      return NormalizeLootRollOptions(options)
    end,
    CUSTOM_LOOT_ROLL_TEMPLATE = CUSTOM_LOOT_ROLL_TEMPLATE,
    NormalizePointMode = function(value)
      return NormalizePointMode(value)
    end,
    EnsureLidTableRows = function(settings)
      return EnsureLidTableRows(settings)
    end,
    ExtractItemID = function(value)
      return ExtractItemID(value)
    end,
    SetLootProfilePermissionMember = function(permissions, targetKey, role)
      return SetLootProfilePermissionMember(permissions, targetKey, role)
    end,
    NormalizeLootProfileRole = function(role)
      return NormalizeLootProfileRole(role)
    end,
    SetLootProfileMemberRole = function(name, characterKey, role, actorCharacterKey)
      return SetLootProfileMemberRole(name, characterKey, role, actorCharacterKey)
    end,
    SetLootProfilePermissionPolicyValue = function(name, role, permissionKey, enabled, actorCharacterKey)
      return SetLootProfilePermissionPolicyValue(name, role, permissionKey, enabled, actorCharacterKey)
    end,
    AddLootLedgerEntry = function(profileName, characterKey, itemName, itemLink, points, note, specID, slotName, icon, itemLevel, opts)
      return AddLootLedgerEntry(profileName, characterKey, itemName, itemLink, points, note, specID, slotName, icon, itemLevel, opts)
    end,
    RemoveLootLedgerEntry = function(profileName, characterKey, entryId)
      return RemoveLootLedgerEntry(profileName, characterKey, entryId)
    end,
  })
end

do
  local lootProfileAccessHelpers = CreateLootProfileAccessHelpers({
    TrimText = TrimText,
    RootDB = function()
      return RootDB()
    end,
    EnsureLootLedgerMeta = function(profile)
      return EnsureLootLedgerMeta(profile)
    end,
    TouchLootProfile = function(profile)
      return TouchLootProfilePermissions(profile)
    end,
    GetCharacterKey = function()
      return GetCharacterKey()
    end,
    GetLootProfile = function(name)
      return GetLootProfile(name)
    end,
    GetActiveLootSession = function()
      if type(GetActiveLootSession) == "function" then
        return GetActiveLootSession()
      end
      return nil
    end,
  })

  NormalizeLootProfileRole = lootProfileAccessHelpers.NormalizeLootProfileRole
  GetSimulationRoleOverride = lootProfileAccessHelpers.GetSimulationRoleOverride
  GetSimulationRoleMode = lootProfileAccessHelpers.GetSimulationRoleMode
  RemoveLootProfilePermissionMember = lootProfileAccessHelpers.RemoveLootProfilePermissionMember
  SetLootProfilePermissionMember = lootProfileAccessHelpers.SetLootProfilePermissionMember
  EnsureLootProfilePermissionState = lootProfileAccessHelpers.EnsureLootProfilePermissionState
  GetLootProfileRoleLocal = lootProfileAccessHelpers.GetLootProfileRoleLocal
  IsLootProfileManagerRole = lootProfileAccessHelpers.IsLootProfileManagerRole
  CollectLootProfileKnownCharacters = lootProfileAccessHelpers.CollectLootProfileKnownCharacters
  GetLootProfileRole = lootProfileAccessHelpers.GetLootProfileRole
  GetLootProfileMaster = lootProfileAccessHelpers.GetLootProfileMaster
  GetLootProfileRoleEntries = lootProfileAccessHelpers.GetLootProfileRoleEntries
  GetLootProfileAccessInfo = lootProfileAccessHelpers.GetLootProfileAccessInfo
  GetLootProfileAccessInfoForRole = lootProfileAccessHelpers.GetLootProfileAccessInfoForRole
  GetLootProfilePermissionPolicyRoleState = lootProfileAccessHelpers.GetLootProfilePermissionPolicyRoleState
  GetLootProfilePermissionPolicyDiagnostics = lootProfileAccessHelpers.GetLootProfilePermissionPolicyDiagnostics
  local rawSetLootProfilePermissionPolicyValue = lootProfileAccessHelpers.SetLootProfilePermissionPolicyValue
  SetLootProfilePermissionPolicyValue = function(name, role, permissionKey, enabled, actorCharacterKey)
    local ok, result = rawSetLootProfilePermissionPolicyValue(name, role, permissionKey, enabled, actorCharacterKey)
    if ok then
      profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "permissions", {
        action = "POLICY_SET",
        domain = "permissions",
        args = { tostring(role or ""), tostring(permissionKey or ""), enabled and "1" or "0" },
      })
    end
    return ok, result
  end
  local rawResetLootProfilePermissionPolicy = lootProfileAccessHelpers.ResetLootProfilePermissionPolicy
  ResetLootProfilePermissionPolicy = function(name, actorCharacterKey)
    local ok, result = rawResetLootProfilePermissionPolicy(name, actorCharacterKey)
    if ok then
      profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "permissions")
    end
    return ok, result
  end
  EnsureLootProfileMemberRole = lootProfileAccessHelpers.EnsureLootProfileMemberRole
  SyncLootProfilePermissionState = lootProfileAccessHelpers.SyncLootProfilePermissionState
  local rawSetLootProfileMemberRole = lootProfileAccessHelpers.SetLootProfileMemberRole
  SetLootProfileMemberRole = function(name, characterKey, role, actorCharacterKey)
    local ok, result = rawSetLootProfileMemberRole(name, characterKey, role, actorCharacterKey)
    if ok then
      profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "permissions", {
        action = "ROLE_SET",
        domain = "permissions",
        args = { tostring(characterKey or ""), tostring(role or "") },
      })
    end
    return ok, result
  end
  local rawTransferLootProfileMaster = lootProfileAccessHelpers.TransferLootProfileMaster
  TransferLootProfileMaster = function(name, characterKey, actorCharacterKey)
    local ok, result, previousMaster = rawTransferLootProfileMaster(name, characterKey, actorCharacterKey)
    if ok then
      profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "permissions")
    end
    return ok, result, previousMaster
  end
  SetSimulationRoleOverride = lootProfileAccessHelpers.SetSimulationRoleOverride
end

local function EnsureLootLedgerMetaEntry(profile, characterKey)
  local resolvedKey = TrimText(characterKey)
  if resolvedKey == "" then
    return nil
  end

  local metaByCharacter = EnsureLootLedgerMeta(profile)
  local entry = type(metaByCharacter[resolvedKey]) == "table" and metaByCharacter[resolvedKey] or {
    penaltyPoints = 0,
    classFile = "",
    className = "",
    specName = "",
    specID = nil,
    specIcon = "",
    note = "",
    mainAltRole = "MAIN",
    mainCharacter = "",
  }
  entry.penaltyPoints = tonumber(entry.penaltyPoints) or 0
  entry.classFile = TrimText(entry.classFile)
  entry.className = TrimText(entry.className)
  entry.specName = TrimText(entry.specName)
  entry.specID = tonumber(entry.specID)
  entry.specID = entry.specID and entry.specID > 0 and math.floor(entry.specID) or nil
  entry.specIcon = TrimText(entry.specIcon)
  entry.note = TrimText(entry.note)
  entry.mainAltRole = NormalizeLootLedgerMainAltRole(entry.mainAltRole)
  entry.mainCharacter = TrimText(entry.mainCharacter)
  if entry.mainAltRole == "MAIN" or string.lower(entry.mainCharacter) == string.lower(resolvedKey) then
    entry.mainCharacter = ""
  end
  metaByCharacter[resolvedKey] = entry
  return entry
end

do
  local lootLedgerHelpers = CreateLootLedgerHelpers({
    TrimText = TrimText,
    ExtractItemID = ExtractItemID,
    EnsureLidTableRows = EnsureLidTableRows,
    NormalizePointMode = NormalizePointMode,
    NormalizeLootRollPointBehavior = NormalizeLootRollPointBehavior,
    GetLootRollPointCounterKey = GetLootRollPointCounterKey,
    NormalizeLootLedgerMainAltRole = NormalizeLootLedgerMainAltRole,
    EnsureLootLedgerMeta = function(profile)
      return EnsureLootLedgerMeta(profile)
    end,
    EnsureLootLedgerMetaEntry = function(profile, characterKey)
      return EnsureLootLedgerMetaEntry(profile, characterKey)
    end,
    GetLootProfile = function(name)
      return GetLootProfile(name)
    end,
    TouchLootProfile = function(profile)
      return TouchLootProfileOperational(profile)
    end,
    GetLootProfileDomainRevision = function(profile, domain)
      if type(GetLootProfileDomainRevision) == "function" then
        return GetLootProfileDomainRevision(profile, domain)
      end
      return 0
    end,
    DeepCopy = function(value)
      return DeepCopy(value)
    end,
  })

  AddLootLedgerEntry = lootLedgerHelpers.AddLootLedgerEntry
  RemoveLootLedgerEntry = lootLedgerHelpers.RemoveLootLedgerEntry
  ResolveLootPointValue = lootLedgerHelpers.ResolveLootPointValue
  ResolveLootAwardOutcome = lootLedgerHelpers.ResolveLootAwardOutcome
  AwardLootToCharacter = lootLedgerHelpers.AwardLootToCharacter
  GetLootLedgerCharacters = lootLedgerHelpers.GetLootLedgerCharacters
end

do
  local lootRollStatsHelpers = CreateLootRollStatsHelpers({
    TrimText = TrimText,
    GetLootProfile = function(name)
      return GetLootProfile(name)
    end,
    TouchLootProfile = function(profile)
      return TouchLootProfileOperational(profile)
    end,
    TouchLootProfileDomain = function(profile, domain)
      if type(TouchLootProfileDomain) == "function" then
        return TouchLootProfileDomain(profile, domain)
      end
      return nil
    end,
    GetLootLedgerCharacters = function(name)
      return GetLootLedgerCharacters(name)
    end,
    DeepCopy = function(value)
      return DeepCopy(value)
    end,
  })

  RecordLootRollStat = lootRollStatsHelpers.RecordLootRollStat
  RecordLootVoteStat = lootRollStatsHelpers.RecordLootVoteStat
  GetLootRollAwards = lootRollStatsHelpers.GetLootRollAwards
  ValidateSimulationVoteExclusion = lootRollStatsHelpers.ValidateSimulationVoteExclusion
end

local function EnsureLootListGroups(profile)
  profile = type(profile) == "table" and profile or {}
  profile.lootListGroups = profile.lootListGroups or {}

  local normalized = {}
  local seen = {}
  for _, group in ipairs(profile.lootListGroups) do
    if type(group) == "table" then
      local groupName = TrimText(group.name)
      local groupKey = string.lower(groupName)
      if groupName ~= "" and not seen[groupKey] then
        seen[groupKey] = true
        local members = {}
        local memberSeen = {}
        for _, memberName in ipairs(group.members or {}) do
          local resolvedMember = TrimText(memberName)
          local memberKey = string.lower(resolvedMember)
          if resolvedMember ~= "" and not memberSeen[memberKey] then
            memberSeen[memberKey] = true
            members[#members + 1] = resolvedMember
          end
        end
        table.sort(members, function(a, b)
          return string.lower(tostring(a)) < string.lower(tostring(b))
        end)
        normalized[#normalized + 1] = {
          name = groupName,
          collapsed = group.collapsed == true,
          members = members,
        }
      end
    end
  end

  table.sort(normalized, function(a, b)
    return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
  end)
  profile.lootListGroups = normalized
  return normalized
end

local function EnsureLootProfileDefaults(profile)
  if type(profile) ~= "table" then
    profile = {}
  end

  profile.settings = profile.settings or {}
  if profile.settings.enabled == nil then
    profile.settings.enabled = false
  else
    profile.settings.enabled = profile.settings.enabled and true or false
  end

  profile.settings.raid = profile.settings.raid or {}
  profile.settings.raid.name = TrimText(profile.settings.raid.name or "")
  if profile.settings.raid.name == "" then
    profile.settings.raid.name = nil
  end
  if profile.settings.raid.instanceID ~= nil then
    profile.settings.raid.instanceID = tonumber(profile.settings.raid.instanceID)
  end

  local rollTemplate = NormalizeLootRollTemplate(profile.settings.rollTemplate)
  if type(profile.settings.rollOptions) ~= "table" or #profile.settings.rollOptions == 0 then
    profile.settings.rollOptions = GetLootRollTemplateOptions(rollTemplate or DEFAULT_LOOT_ROLL_TEMPLATE)
    rollTemplate = rollTemplate or DEFAULT_LOOT_ROLL_TEMPLATE
  end
  for i = #profile.settings.rollOptions, 1, -1 do
    local option = profile.settings.rollOptions[i]
    if type(option) ~= "table" then
      table.remove(profile.settings.rollOptions, i)
    else
      option.key = NormalizeLootRollOptionKey(option.key ~= "" and option.key or option.label or ("option" .. tostring(i)))
      option.label = TrimText(option.label or option.key or ("Option " .. tostring(i)))
      option.color = option.color or { 0.82, 0.82, 0.86 }
      option.icon = TrimText(option.icon)
      if option.icon == "" then
        option.icon = ResolveDefaultRollOptionIcon(option.key, option.label)
      end
      option.pointBehavior = NormalizeLootRollPointBehavior(option.pointBehavior, option.key, option.label)
      option.popupSection = NormalizeLootRollPopupSection(option.popupSection, option.pointBehavior, option.key, option.label)
    end
  end
  profile.settings.rollOptions = NormalizeLootRollOptions(profile.settings.rollOptions)
  if #profile.settings.rollOptions == 0 then
    profile.settings.rollOptions = GetLootRollTemplateOptions(DEFAULT_LOOT_ROLL_TEMPLATE)
    rollTemplate = DEFAULT_LOOT_ROLL_TEMPLATE
  end
  if rollTemplate == nil then
    rollTemplate = DetectLootRollTemplate(profile.settings.rollOptions)
  end
  profile.settings.rollTemplate = rollTemplate

  profile.settings.pointMode = NormalizePointMode(profile.settings.pointMode)
  EnsureLidTableRows(profile.settings)

  profile.settings.pointRules = profile.settings.pointRules or {}
  for i = #profile.settings.pointRules, 1, -1 do
    local rule = profile.settings.pointRules[i]
    if type(rule) ~= "table" then
      table.remove(profile.settings.pointRules, i)
    else
      rule.mode = (rule.mode == "item") and "item" or "itemLevel"
      if rule.mode == "itemLevel" then
        rule.itemLevel = tonumber(rule.itemLevel)
      else
        rule.itemID = ExtractItemID(rule.itemID or rule.itemLink)
        rule.itemName = TrimText(rule.itemName or "")
        rule.itemLink = TrimText(rule.itemLink or "")
      end
      rule.points = tonumber(rule.points) or 0
    end
  end

  profile.sessions = profile.sessions or {}
  for _, session in ipairs(profile.sessions) do
    if type(session) == "table" then
      session.autoPassEnabled = nil
      if type(session.rollOptions) == "table" and #session.rollOptions > 0 then
        session.rollOptions = NormalizeLootRollOptions(session.rollOptions)
      else
        session.rollOptions = NormalizeLootRollOptions(profile.settings.rollOptions)
      end
      session.members = session.members or {}
    end
  end
  profile.lootLedger = profile.lootLedger or {}
  profile.lootRollStats = type(profile.lootRollStats) == "table" and profile.lootRollStats or {}
  EnsureLootLedgerMeta(profile)
  EnsureLootListGroups(profile)
  profile.meta = profile.meta or {}
  if profile.meta.createdAt == nil then
    profile.meta.createdAt = time and time() or 0
  end
  profile.meta.revision = math.max(0, tonumber(profile.meta.revision) or 0)
  profile.meta.updatedAt = tonumber(profile.meta.updatedAt) or profile.meta.createdAt
  EnsureLootProfilePermissionState(profile)
  if type(EnsureLootProfileIdentity) == "function" then
    EnsureLootProfileIdentity(profile, nil, nil, LidCouncilDB)
  end
  if type(EnsureLootProfileSyncState) == "function" then
    EnsureLootProfileSyncState(profile)
  end
  if type(EnsureLootProfileRevisionState) == "function" then
    EnsureLootProfileRevisionState(profile)
  end
  return profile
end

do
  local lootProfileStateHelpers = CreateLootProfileStateHelpers({
    TrimText = TrimText,
    RootDB = function()
      return RootDB()
    end,
    DeepCopy = function(value)
      return DeepCopy(value)
    end,
    EnsureLootProfileDefaults = EnsureLootProfileDefaults,
    EnsureLootProfilePermissionState = function(profile, fallbackMasterKey)
      return EnsureLootProfilePermissionState(profile, fallbackMasterKey)
    end,
    GetCharacterKey = function()
      return GetCharacterKey()
    end,
    GetLootProfileAccessInfo = function(name)
      return GetLootProfileAccessInfo(name)
    end,
    EnsureLootRootState = function(root)
      return EnsureLootRootState(root)
    end,
    GetActiveLootSession = function()
      return GetActiveLootSession()
    end,
    AssignNewLootProfileIdentity = function(profile, profileName)
      if type(AssignNewLootProfileIdentity) == "function" then
        return AssignNewLootProfileIdentity(profile, profileName)
      end
      return nil
    end,
    MarkLootProfileAsIndependentClone = function(profile, origin, sourceInfo)
      if type(MarkLootProfileAsIndependentClone) == "function" then
        return MarkLootProfileAsIndependentClone(profile, origin, sourceInfo)
      end
      return nil
    end,
    TouchLootProfileDomain = function(profile, domain)
      if type(TouchLootProfileDomain) == "function" then
        return TouchLootProfileDomain(profile, domain)
      end
      return nil
    end,
    CaptureCurrentLootSeasonSnapshot = function()
      return CaptureCurrentLootSeasonSnapshot()
    end,
  })

  NormalizeLootProfileEnabledState = lootProfileStateHelpers.NormalizeLootProfileEnabledState
  EnsureProfileRecord = lootProfileStateHelpers.EnsureProfileRecord
  RepairSimulationProfileOwnership = lootProfileStateHelpers.RepairSimulationProfileOwnership
  TouchLootProfile = lootProfileStateHelpers.TouchLootProfile
  GetLootProfile = lootProfileStateHelpers.GetLootProfile
  GetLootProfileNames = lootProfileStateHelpers.GetLootProfileNames
  SetCurrentLootProfile = lootProfileStateHelpers.SetCurrentLootProfile
  GetEnabledLootProfileName = lootProfileStateHelpers.GetEnabledLootProfileName
  GetPrimaryLootProfileName = lootProfileStateHelpers.GetPrimaryLootProfileName
  GetLootProfileId = lootProfileStateHelpers.GetLootProfileId
  GetLootProfileIdentityInfo = lootProfileStateHelpers.GetLootProfileIdentityInfo
  FindLootProfileNameById = lootProfileStateHelpers.FindLootProfileNameById
  CreateLootProfile = lootProfileStateHelpers.CreateLootProfile
  DeleteLootProfile = lootProfileStateHelpers.DeleteLootProfile
  CopyLootProfile = lootProfileStateHelpers.CopyLootProfile
  ResetSimulationProfileContent = lootProfileStateHelpers.ResetSimulationProfileContent
  SetLootProfileEnabled = lootProfileStateHelpers.SetLootProfileEnabled
end

NormalizeLootRollOptions = function(options)
  local normalized = {}
  local seenKeys = {}
  local function buildUniqueKey(rawKey)
    local baseKey = NormalizeLootRollOptionKey(rawKey, "option")
    local candidate = baseKey
    local suffix = 2
    while seenKeys[candidate] do
      candidate = string.format("%s_%d", baseKey, suffix)
      suffix = suffix + 1
    end
    seenKeys[candidate] = true
    return candidate
  end
  if type(options) == "table" then
    for i, option in ipairs(options) do
      if type(option) == "table" then
        local label = TrimText(option.label or option.key or ("Option " .. tostring(i)))
        if label ~= "" then
          normalized[#normalized + 1] = {
            key = buildUniqueKey(option.key ~= "" and option.key or label),
            label = label,
            color = {
              tonumber(option.color and option.color[1]) or 0.82,
              tonumber(option.color and option.color[2]) or 0.82,
              tonumber(option.color and option.color[3]) or 0.86,
            },
            icon = (function()
              local resolvedIcon = TrimText(option.icon)
              if resolvedIcon ~= "" then
                return resolvedIcon
              end
              return ResolveDefaultRollOptionIcon(option.key, label)
            end)(),
            pointBehavior = NormalizeLootRollPointBehavior(option.pointBehavior, option.key, label),
            popupSection = NormalizeLootRollPopupSection(option.popupSection, option.pointBehavior, option.key, label),
          }
        end
      end
    end
  end
  if #normalized == 0 then
    return DeepCopy(DEFAULT_LOOT_ROLL_OPTIONS)
  end
  return normalized
end

GetLootRollTemplateOptions = function(templateValue)
  local resolvedTemplate = NormalizeLootRollTemplate(templateValue)
  if resolvedTemplate == LIDCOUNCIL_LOOT_ROLL_TEMPLATE then
    return NormalizeLootRollOptions(CreateLidCouncilLootRollTemplateOptions())
  end
  return DeepCopy(DEFAULT_LOOT_ROLL_OPTIONS)
end

local function AreLootRollOptionsEquivalent(leftOptions, rightOptions)
  local left = NormalizeLootRollOptions(leftOptions)
  local right = NormalizeLootRollOptions(rightOptions)
  if #left ~= #right then
    return false
  end

  for index = 1, #left do
    local leftOption = left[index]
    local rightOption = right[index]
    if tostring(leftOption.key or "") ~= tostring(rightOption.key or "") then
      return false
    end
    if tostring(leftOption.label or "") ~= tostring(rightOption.label or "") then
      return false
    end
    if tostring(leftOption.icon or "") ~= tostring(rightOption.icon or "") then
      return false
    end
    if tostring(leftOption.pointBehavior or "") ~= tostring(rightOption.pointBehavior or "") then
      return false
    end
    if tostring(leftOption.popupSection or "") ~= tostring(rightOption.popupSection or "") then
      return false
    end
    for colorIndex = 1, 3 do
      local leftColor = tonumber(leftOption.color and leftOption.color[colorIndex]) or 0
      local rightColor = tonumber(rightOption.color and rightOption.color[colorIndex]) or 0
      if math.abs(leftColor - rightColor) > 0.0001 then
        return false
      end
    end
  end

  return true
end

DetectLootRollTemplate = function(options)
  if AreLootRollOptionsEquivalent(options, CreateLidCouncilLootRollTemplateOptions()) then
    return LIDCOUNCIL_LOOT_ROLL_TEMPLATE
  end
  if AreLootRollOptionsEquivalent(options, DEFAULT_LOOT_ROLL_OPTIONS) then
    return DEFAULT_LOOT_ROLL_TEMPLATE
  end
  return CUSTOM_LOOT_ROLL_TEMPLATE
end

local function IsCombatLocked()
  return type(InCombatLockdown) == "function" and InCombatLockdown() and true or false
end

local function RejectCombatLockedProfileMutation(actionLabel)
  if not IsCombatLocked() then
    return false
  end
  local message = tostring(actionLabel or "This profile action") .. " is unavailable during combat."
  if type(Print) == "function" then
    Print(message)
  end
  return true, message
end

function SetLootProfileRaid(name, raidName, instanceID)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Raid Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditRaidSettings then
    return false, "Your current role cannot edit raid settings for this profile."
  end
  local profile = GetLootProfile(name)
  if type(profile) ~= "table" then
    return false, "Profile not found."
  end
  local resolvedInstanceID = tonumber(instanceID)
  local seasonSnapshot = type(profile) == "table"
    and type(profile.settings) == "table"
    and type(profile.settings.seasonSnapshot) == "table"
    and profile.settings.seasonSnapshot
    or nil
  if resolvedInstanceID and type(seasonSnapshot) == "table" and type(seasonSnapshot.raids) == "table" and #seasonSnapshot.raids > 0 then
    local allowed = false
    for _, raid in ipairs(seasonSnapshot.raids) do
      if tonumber(raid.instanceID) == resolvedInstanceID then
        allowed = true
        break
      end
    end
    if not allowed then
      return false, "This raid is not part of the profile season. Create a new profile for a new season."
    end
  end
  profile.settings.raid = profile.settings.raid or {}
  local cleanName = TrimText(raidName)
  profile.settings.raid.name = (cleanName ~= "") and cleanName or nil
  profile.settings.raid.instanceID = resolvedInstanceID
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "SET_RAID",
    domain = "settings",
    args = { cleanName, tostring(profile.settings.raid.instanceID or "") },
  })
  return true
end

function ApplyLootRollTemplate(name, templateValue)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Roll Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditRollSettings then
    return false, "Your current role cannot edit roll settings for this profile."
  end

  local resolvedTemplate = NormalizeLootRollTemplate(templateValue)
  if resolvedTemplate ~= DEFAULT_LOOT_ROLL_TEMPLATE and resolvedTemplate ~= LIDCOUNCIL_LOOT_ROLL_TEMPLATE then
    return false, "Roll template not found."
  end

  local profile = GetLootProfile(name)
  profile.settings.rollTemplate = resolvedTemplate
  profile.settings.rollOptions = GetLootRollTemplateOptions(resolvedTemplate)
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "ROLL_TEMPLATE",
    domain = "settings",
    args = { resolvedTemplate },
  })
  return true, DeepCopy(profile.settings.rollOptions)
end

function AddLootRollOption(name, label)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Roll Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditRollSettings then
    return false, "Your current role cannot edit roll settings for this profile."
  end
  local profile = GetLootProfile(name)
  local cleanLabel = TrimText(label)
  if cleanLabel == "" then
    return false, "Roll option label is required."
  end

  profile.settings.rollOptions = profile.settings.rollOptions or DeepCopy(DEFAULT_LOOT_ROLL_OPTIONS)
  local options = profile.settings.rollOptions
  options[#options + 1] = {
    key = NormalizeLootRollOptionKey(cleanLabel, cleanLabel),
    label = cleanLabel,
    color = { 0.82, 0.82, 0.86 },
    icon = ResolveDefaultRollOptionIcon(cleanLabel, cleanLabel),
    pointBehavior = NormalizeLootRollPointBehavior(nil, cleanLabel, cleanLabel),
    popupSection = NormalizeLootRollPopupSection(nil, nil, cleanLabel, cleanLabel),
  }
  profile.settings.rollOptions = NormalizeLootRollOptions(options)
  profile.settings.rollTemplate = CUSTOM_LOOT_ROLL_TEMPLATE
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "ROLL_OPTION_ADD",
    domain = "settings",
    args = profileSyncSupportHelpers.BuildRollOptionDeltaArgs(#profile.settings.rollOptions, profile.settings.rollOptions[#profile.settings.rollOptions]),
  })
  return true
end

function UpdateLootRollOption(name, index, changes)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Roll Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditRollSettings then
    return false, "Your current role cannot edit roll settings for this profile."
  end
  local profile = GetLootProfile(name)
  local options = profile.settings.rollOptions or {}
  local idx = tonumber(index)
  if not idx or idx < 1 or idx > #options then
    return false, "Roll option not found."
  end

  local option = options[idx]
  changes = type(changes) == "table" and changes or {}

  if changes.label ~= nil then
    local cleanLabel = TrimText(changes.label)
    if cleanLabel == "" then
      return false, "Roll option label is required."
    end
    option.label = cleanLabel
    option.key = NormalizeLootRollOptionKey(cleanLabel, cleanLabel)
  end

  if changes.color ~= nil then
    local color = type(changes.color) == "table" and changes.color or {}
    option.color = {
      tonumber(color[1]) or tonumber(color.r) or 0.82,
      tonumber(color[2]) or tonumber(color.g) or 0.82,
      tonumber(color[3]) or tonumber(color.b) or 0.86,
    }
  end

  if changes.icon ~= nil then
    local resolvedIcon = (changes.icon == false) and "" or TrimText(changes.icon)
    option.icon = (resolvedIcon ~= "") and resolvedIcon or nil
  end

  if changes.pointBehavior ~= nil then
    option.pointBehavior = NormalizeLootRollPointBehavior(changes.pointBehavior, option.key, option.label)
  end

  if changes.popupSection ~= nil then
    option.popupSection = NormalizeLootRollPopupSection(changes.popupSection, option.pointBehavior, option.key, option.label)
  end

  profile.settings.rollOptions = NormalizeLootRollOptions(options)
  profile.settings.rollTemplate = CUSTOM_LOOT_ROLL_TEMPLATE
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "ROLL_OPTION_UPDATE",
    domain = "settings",
    args = profileSyncSupportHelpers.BuildRollOptionDeltaArgs(idx, profile.settings.rollOptions[idx]),
  })
  return true, DeepCopy(profile.settings.rollOptions[idx])
end

function MoveLootRollOption(name, index, direction)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Roll Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditRollSettings then
    return false, "Your current role cannot edit roll settings for this profile."
  end

  local profile = GetLootProfile(name)
  local options = profile.settings.rollOptions or {}
  local idx = tonumber(index)
  local delta = tonumber(direction)
  if not idx or idx < 1 or idx > #options then
    return false, "Roll option not found."
  end
  if not delta or delta == 0 then
    return false, "Move direction is required."
  end

  local targetIndex = idx + (delta > 0 and 1 or -1)
  if targetIndex < 1 or targetIndex > #options then
    return false, "Roll option is already at the edge."
  end

  options[idx], options[targetIndex] = options[targetIndex], options[idx]
  profile.settings.rollTemplate = CUSTOM_LOOT_ROLL_TEMPLATE
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "ROLL_OPTION_MOVE",
    domain = "settings",
    args = { tostring(idx), tostring(targetIndex) },
  })
  return true, targetIndex
end

function RemoveLootRollOption(name, index)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Roll Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditRollSettings then
    return false, "Your current role cannot edit roll settings for this profile."
  end
  local profile = GetLootProfile(name)
  local options = profile.settings.rollOptions or {}
  local idx = tonumber(index)
  if not idx or idx < 1 or idx > #options then
    return false
  end
  if #options <= 1 then
    return false
  end
  table.remove(options, idx)
  profile.settings.rollTemplate = CUSTOM_LOOT_ROLL_TEMPLATE
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "ROLL_OPTION_REMOVE",
    domain = "settings",
    args = { tostring(idx) },
  })
  return true
end

function UpdateLootPointMode(name, mode)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Point Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditPointSettings then
    return false, "Your current role cannot edit point settings for this profile."
  end
  local profile = GetLootProfile(name)
  profile.settings.pointMode = NormalizePointMode(mode)
  EnsureLidTableRows(profile.settings)
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "POINT_MODE",
    domain = "settings",
    args = { profile.settings.pointMode },
  })
  return profile.settings.pointMode
end

function UpdateLootLidTableRow(name, index, values)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Point Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditPointSettings then
    return false, "Your current role cannot edit point settings for this profile."
  end
  local profile = GetLootProfile(name)
  local rows = EnsureLidTableRows(profile.settings)
  local idx = tonumber(index)
  if not idx or idx < 1 or idx > 15 then
    return false, "LidTable row not found."
  end

  values = type(values) == "table" and values or {}
  local row = rows[idx]

  local pointsValue = values.points
  if pointsValue ~= nil then
    local textValue = TrimText(pointsValue)
    if textValue == "" then
      row.points = idx
    else
      local resolvedPoints = tonumber(textValue)
      if not resolvedPoints then
        return false, "Points must be numeric."
      end
      row.points = resolvedPoints
    end
  end

  local itemLevelValue = values.itemLevel
  if itemLevelValue ~= nil then
    local textValue = TrimText(itemLevelValue)
    if textValue == "" then
      row.itemLevel = nil
    else
      local resolvedLevel = tonumber(textValue)
      if not resolvedLevel then
        return false, "Item level must be numeric."
      end
      row.itemLevel = resolvedLevel
    end
  end

  local setPieceValue = nil
  if values.setPiecePoints ~= nil then
    setPieceValue = values.setPiecePoints
  elseif values.setPiece ~= nil then
    setPieceValue = values.setPiece
  end
  if setPieceValue ~= nil then
    local textValue = TrimText(setPieceValue)
    if textValue == "" then
      row.setPiecePoints = nil
    else
      local resolvedPoints = tonumber(textValue)
      if not resolvedPoints then
        return false, "Setpiece points must be numeric."
      end
      row.setPiecePoints = resolvedPoints
    end
  end

  if values.offSpecPoints ~= nil then
    local textValue = TrimText(values.offSpecPoints)
    if textValue == "" then
      row.offSpecPoints = nil
    else
      local resolvedPoints = tonumber(textValue)
      if not resolvedPoints then
        return false, "Off-Spec points must be numeric."
      end
      row.offSpecPoints = resolvedPoints
    end
  end

  if values.transmogPoints ~= nil then
    local textValue = TrimText(values.transmogPoints)
    if textValue == "" then
      row.transmogPoints = nil
    else
      local resolvedPoints = tonumber(textValue)
      if not resolvedPoints then
        return false, "Transmog points must be numeric."
      end
      row.transmogPoints = resolvedPoints
    end
  end

  if values.disenchantPoints ~= nil then
    local textValue = TrimText(values.disenchantPoints)
    if textValue == "" then
      row.disenchantPoints = nil
    else
      local resolvedPoints = tonumber(textValue)
      if not resolvedPoints then
        return false, "Disenchant points must be numeric."
      end
      row.disenchantPoints = resolvedPoints
    end
  end

  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "LID_TABLE_ROW",
    domain = "settings",
    args = profileSyncSupportHelpers.BuildLidTableRowDeltaArgs(idx, row),
  })
  return true, DeepCopy(row)
end

function AddLootPointRule(name, rule)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Point Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditPointSettings then
    return false, "Your current role cannot edit point settings for this profile."
  end
  local profile = GetLootProfile(name)
  rule = type(rule) == "table" and DeepCopy(rule) or {}
  rule.mode = (rule.mode == "item") and "item" or "itemLevel"
  rule.points = tonumber(rule.points) or 0
  if rule.mode == "itemLevel" then
    rule.itemLevel = tonumber(rule.itemLevel)
    if not rule.itemLevel then
      return false, "Item level is required."
    end
  else
    rule.itemID = ExtractItemID(rule.itemID or rule.itemLink)
    rule.itemName = TrimText(rule.itemName)
    rule.itemLink = TrimText(rule.itemLink)
    if rule.itemName == "" and rule.itemLink ~= "" then
      local linkName = rule.itemLink:match("%[(.-)%]")
      if linkName then
        rule.itemName = TrimText(linkName)
      end
    end
    if not rule.itemID and rule.itemName == "" then
      return false, "Item selection is required."
    end
  end
  profile.settings.pointRules[#profile.settings.pointRules + 1] = rule
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "POINT_RULE_ADD",
    domain = "settings",
    args = {
      tostring(rule.mode or "itemLevel"),
      tostring(rule.itemLevel or ""),
      tostring(rule.points or 0),
      tostring(rule.itemID or ""),
      TrimText(rule.itemName),
      TrimText(rule.itemLink),
    },
  })
  return true
end

function RemoveLootPointRule(name, index)
  local blocked, blockedMessage = RejectCombatLockedProfileMutation("Edit Point Settings")
  if blocked then
    return false, blockedMessage
  end
  local access = GetLootProfileAccessInfo(name)
  if not access.canEditPointSettings then
    return false, "Your current role cannot edit point settings for this profile."
  end
  local profile = GetLootProfile(name)
  local rules = profile.settings.pointRules or {}
  local idx = tonumber(index)
  if not idx or idx < 1 or idx > #rules then
    return false
  end
  table.remove(rules, idx)
  TouchLootProfileSettings(profile)
  profileSyncSupportHelpers.BroadcastActiveProfileChange(name, "settings", {
    action = "POINT_RULE_REMOVE",
    domain = "settings",
    args = { tostring(idx) },
  })
  return true
end

function GetLocalAutoPassEnabled()
  local root = RootDB()
  root.ui = type(root.ui) == "table" and root.ui or {}
  if root.ui.localAutoPassEnabled == nil then
    root.ui.localAutoPassEnabled = true
  else
    root.ui.localAutoPassEnabled = root.ui.localAutoPassEnabled and true or false
  end
  return root.ui.localAutoPassEnabled
end

function SetLocalAutoPassEnabled(enabled)
  local root = RootDB()
  root.ui = type(root.ui) == "table" and root.ui or {}
  root.ui.localAutoPassEnabled = enabled and true or false
  return root.ui.localAutoPassEnabled
end

do
  local lootSessionHelpers = CreateLootSessionHelpers({
    TrimText = TrimText,
    DeepCopy = function(value)
      return DeepCopy(value)
    end,
    RootDB = function()
      return RootDB()
    end,
    GetLootProfile = function(name)
      return GetLootProfile(name)
    end,
    EnsureLootProfileDefaults = function(profile)
      return EnsureLootProfileDefaults(profile)
    end,
    GetCharacterKey = function()
      return GetCharacterKey()
    end,
    NormalizeLootRollOptions = NormalizeLootRollOptions,
    EnsureLootProfilePermissionState = function(profile, fallbackMasterKey)
      return EnsureLootProfilePermissionState(profile, fallbackMasterKey)
    end,
    GetLootProfileMaster = function(name)
      return GetLootProfileMaster(name)
    end,
    GetLootProfileAccessInfo = function(name)
      return GetLootProfileAccessInfo(name)
    end,
    GetLocalAutoPassEnabled = function()
      return GetLocalAutoPassEnabled()
    end,
    NormalizeLootProfileRole = NormalizeLootProfileRole,
    GetLootProfileRoleLocal = function(profile, characterKey)
      return GetLootProfileRoleLocal(profile, characterKey)
    end,
    EnsureLootLedgerMetaEntry = function(profile, characterKey)
      return EnsureLootLedgerMetaEntry(profile, characterKey)
    end,
    TouchLootProfile = function(profile)
      return TouchLootProfileOperational(profile)
    end,
    TouchLoot = function(profile)
      return TouchLootProfileLoot(profile)
    end,
    TouchSession = function(profile)
      return TouchLootProfileSession(profile)
    end,
    TouchPermissions = function(profile)
      return TouchLootProfilePermissions(profile)
    end,
    RemoveLootProfilePermissionMember = RemoveLootProfilePermissionMember,
    SetLootProfilePermissionMember = SetLootProfilePermissionMember,
  })

  EnsureLootRootState = lootSessionHelpers.EnsureLootRootState
  SetLootRootSessionState = lootSessionHelpers.SetLootRootSessionState
  FindActiveSessionByState = lootSessionHelpers.FindActiveSessionByState
  CollectActiveLootSessions = lootSessionHelpers.CollectActiveLootSessions
  NormalizeActiveLootSessions = lootSessionHelpers.NormalizeActiveLootSessions
  FindLootSession = lootSessionHelpers.FindLootSession
  StartLootSession = lootSessionHelpers.StartLootSession
  ActivateLootSession = lootSessionHelpers.ActivateLootSession
  GetLootSession = lootSessionHelpers.GetLootSession
  JoinLootSession = lootSessionHelpers.JoinLootSession
  GetActiveLootSession = lootSessionHelpers.GetActiveLootSession
  StopLootSession = lootSessionHelpers.StopLootSession
  AddLootSessionMember = lootSessionHelpers.AddLootSessionMember
  UpsertLootSessionMember = lootSessionHelpers.UpsertLootSessionMember
  RemoveLootSessionMember = lootSessionHelpers.RemoveLootSessionMember
  FinishLootSession = lootSessionHelpers.FinishLootSession
  GetEnglishClassAndSpec = lootSessionHelpers.GetEnglishClassAndSpec
end
do
  coreSystemHelpers = CreateCoreSystemHelpers({
    Print = Print,
    RootDB = function()
      return RootDB()
    end,
    DB = function()
      return DB()
    end,
    TrimText = TrimText,
    EnsureProfileRecord = function(root, name)
      return EnsureProfileRecord(root, name)
    end,
    RepairSimulationProfileOwnership = function(root)
      if type(RepairSimulationProfileOwnership) == "function" then
        return RepairSimulationProfileOwnership(root)
      end
      return false, "UNAVAILABLE"
    end,
    EnsureLootProfilePermissionState = function(profile, fallbackMasterKey)
      return EnsureLootProfilePermissionState(profile, fallbackMasterKey)
    end,
    EnsureLootProfileDefaults = function(profile)
      return EnsureLootProfileDefaults(profile)
    end,
    NormalizeActiveLootSessions = function(root)
      return NormalizeActiveLootSessions(root)
    end,
    NormalizeLootProfileEnabledState = function(root, preferredProfileName)
      return NormalizeLootProfileEnabledState(root, preferredProfileName)
    end,
    GetLootProfile = function(name)
      return GetLootProfile(name)
    end,
    GetLootProfileAccessInfo = function(name, characterKey)
      return GetLootProfileAccessInfo(name, characterKey)
    end,
    EnsureLidTableRows = function(settings)
      return EnsureLidTableRows(settings)
    end,
    NormalizePointMode = function(mode)
      return NormalizePointMode(mode)
    end,
    TouchLootProfile = function(profile)
      return TouchLootProfileOperational(profile)
    end,
    TouchLootProfileSettings = function(profile)
      return TouchLootProfileSettings(profile)
    end,
    TouchLootProfilePermissions = function(profile)
      return TouchLootProfilePermissions(profile)
    end,
    TouchLootProfileDomain = function(profile, domain)
      if type(TouchLootProfileDomain) == "function" then
        return TouchLootProfileDomain(profile, domain)
      end
      return nil
    end,
  })

  DeepCopy = coreSystemHelpers.DeepCopy
  GetSpecKey = coreSystemHelpers.GetSpecKey
  GetCurrentSpecID = coreSystemHelpers.GetCurrentSpecID
  GetCharacterKey = coreSystemHelpers.GetCharacterKey
  AddCharacterToList = coreSystemHelpers.AddCharacterToList
  GetCharacterList = coreSystemHelpers.GetCharacterList
  LoadLayoutFromSpec = coreSystemHelpers.LoadLayoutFromSpec
  EnsureDefaults = coreSystemHelpers.EnsureDefaults
  RunRootMigrations = coreSystemHelpers.RunRootMigrations
  EnsureLootProfileIdentity = coreSystemHelpers.EnsureLootProfileIdentity
  AssignNewLootProfileIdentity = coreSystemHelpers.AssignNewLootProfileIdentity
  EnsureLootProfileSyncState = coreSystemHelpers.EnsureLootProfileSyncState
  MarkLootProfileAsIndependentClone = coreSystemHelpers.MarkLootProfileAsIndependentClone
  EnsureLootProfileRevisionState = coreSystemHelpers.EnsureLootProfileRevisionState
  BuildLootProfileSnapshotDiff = coreSystemHelpers.BuildLootProfileSnapshotDiff
  BuildLootProfileDomainSnapshotDiff = coreSystemHelpers.BuildLootProfileDomainSnapshotDiff
  BuildLootProfileSubdomainSnapshotDiff = coreSystemHelpers.BuildLootProfileSubdomainSnapshotDiff
  BuildLootProfileSubdomainPatchDiff = coreSystemHelpers.BuildLootProfileSubdomainPatchDiff
  BuildLootProfileTransferConflictPreview = coreSystemHelpers.BuildLootProfileTransferConflictPreview
  ApplyLootProfileTransferConflictResolution = coreSystemHelpers.ApplyLootProfileTransferConflictResolution
  GetLootProfileRevisionSummary = coreSystemHelpers.GetLootProfileRevisionSummary
  GetLootProfileDomainRevision = coreSystemHelpers.GetLootProfileDomainRevision
  NormalizeLootProfileSubdomainList = coreSystemHelpers.NormalizeLootProfileSubdomainList
  CloneLootProfileSubdomainList = coreSystemHelpers.CloneLootProfileSubdomainList
  FilterLootProfilePatchCapableSubdomains = coreSystemHelpers.FilterLootProfilePatchCapableSubdomains
  GetLootProfileSubdomainKeysForDomain = coreSystemHelpers.GetLootProfileSubdomainKeysForDomain
  GetLootProfileSubdomainLabel = coreSystemHelpers.GetLootProfileSubdomainLabel
  GetLootProfileSubdomainParentDomain = coreSystemHelpers.GetLootProfileSubdomainParentDomain
  ResolveLootProfileDomainsForSubdomains = coreSystemHelpers.ResolveLootProfileDomainsForSubdomains
  GetLootProfileSubchecksumManifest = coreSystemHelpers.GetLootProfileSubchecksumManifest
  TouchLootProfileDomain = coreSystemHelpers.TouchLootProfileDomain
end

-- ============================================================

local profileBackupHelpers
do
  local createLootProfileBackupHelpers = rawget(_G, "LIDCOUNCIL_CreateLootProfileBackupHelpers_Impl")
  if type(createLootProfileBackupHelpers) ~= "function" then
    error("LidCouncil missing loot profile backup helpers.")
  end
  profileBackupHelpers = createLootProfileBackupHelpers({
    TrimText = TrimText,
    RootDB = function()
      return RootDB()
    end,
    DeepCopy = function(value)
      return DeepCopy(value)
    end,
    GetLootProfile = function(name)
      return GetLootProfile(name)
    end,
    GetLootProfileIdentityInfo = function(name)
      if type(GetLootProfileIdentityInfo) == "function" then
        return GetLootProfileIdentityInfo(name)
      end
      return nil
    end,
    GetActiveLootSession = function()
      if type(GetActiveLootSession) == "function" then
        return GetActiveLootSession()
      end
      return nil
    end,
    TouchLootProfileOperational = function(profile)
      return TouchLootProfileOperational(profile)
    end,
  })
end

-- ============================================================

-- Stable, namespaced API surface for cross-file/internal lookups.
local EnsureApiRegistry = rawget(_G, "LIDCOUNCIL_EnsureApiRegistry")
local API, INTERNAL_API
if type(EnsureApiRegistry) == "function" then
  API, INTERNAL_API = EnsureApiRegistry()
else
  API = _G.LIDCOUNCIL_API
  if type(API) ~= "table" then
    API = {}
  end
  if type(API.__internal) ~= "table" then
    API.__internal = {}
  end
  _G.LIDCOUNCIL_API = API
  INTERNAL_API = API.__internal
end

local function PublishApi(name, value, opts)
  API[name] = value
  if type(opts) == "table" and opts.global == false then
    _G[name] = nil
    return
  end
  _G[name] = value
end

INTERNAL_API.LIDCOUNCIL_RunAuthoritativeLootArchiveSerialValidation_Impl =
  coreSystemHelpers.RunAuthoritativeLootArchiveSerialValidation

PublishApi("LIDCOUNCIL_ADDON_NAME", _G.LIDCOUNCIL_ADDON_NAME)
PublishApi("LIDCOUNCIL_RootDB", RootDB)
PublishApi("LIDCOUNCIL_DB", DB)
PublishApi("LIDCOUNCIL_DeepCopy", DeepCopy)
PublishApi("LIDCOUNCIL_Print", Print)
PublishApi("LIDCOUNCIL_ExportProfile", coreSystemHelpers.ExportProfile)
PublishApi("LIDCOUNCIL_ImportProfile", coreSystemHelpers.ImportProfile)
PublishApi("LIDCOUNCIL_ExportLootProfileSnapshot", coreSystemHelpers.ExportLootProfileSnapshot)
PublishApi("LIDCOUNCIL_ImportLootProfileSnapshot", coreSystemHelpers.ImportLootProfileSnapshot)
PublishApi("LIDCOUNCIL_PreviewLootProfileSnapshot", coreSystemHelpers.PreviewLootProfileSnapshot)
PublishApi("LIDCOUNCIL_ExportLootRollSettings", coreSystemHelpers.ExportLootRollSettings)
PublishApi("LIDCOUNCIL_ImportLootRollSettings", coreSystemHelpers.ImportLootRollSettings)
PublishApi("LIDCOUNCIL_CopyLootRollSettings", coreSystemHelpers.CopyLootRollSettings)
PublishApi("LIDCOUNCIL_ExportLootPointSettings", coreSystemHelpers.ExportLootPointSettings)
PublishApi("LIDCOUNCIL_ImportLootPointSettings", coreSystemHelpers.ImportLootPointSettings)
PublishApi("LIDCOUNCIL_CopyLootPointSettings", coreSystemHelpers.CopyLootPointSettings)
PublishApi("LIDCOUNCIL_GetLootProfileBackups", profileBackupHelpers.GetLootProfileBackups, { global = false })
PublishApi("LIDCOUNCIL_SaveLootProfileBackup", profileBackupHelpers.SaveLootProfileBackup, { global = false })
PublishApi("LIDCOUNCIL_LoadLootProfileBackup", profileBackupHelpers.LoadLootProfileBackup, { global = false })
PublishApi("LIDCOUNCIL_DeleteLootProfileBackup", profileBackupHelpers.DeleteLootProfileBackup, { global = false })
PublishApi("LIDCOUNCIL_RecordLootRollStat", RecordLootRollStat, { global = false })
PublishApi("LIDCOUNCIL_RecordLootVoteStat", RecordLootVoteStat, { global = false })
PublishApi("LIDCOUNCIL_GetLootRollAwards", GetLootRollAwards, { global = false })
PublishApi("LIDCOUNCIL_ValidateSimulationVoteExclusion", ValidateSimulationVoteExclusion, { global = false })
PublishApi("LIDCOUNCIL_GetSpecKey", GetSpecKey)
PublishApi("LIDCOUNCIL_GetCurrentSpecID", GetCurrentSpecID)
PublishApi("LIDCOUNCIL_GetEnglishClassAndSpec", GetEnglishClassAndSpec, { global = false })
PublishApi("LIDCOUNCIL_GetCharacterKey", GetCharacterKey)
PublishApi("LIDCOUNCIL_AddCharacterToList", AddCharacterToList)
PublishApi("LIDCOUNCIL_GetCharacterList", GetCharacterList)
PublishApi("LIDCOUNCIL_LoadLayoutFromSpec", LoadLayoutFromSpec)
PublishApi("LIDCOUNCIL_EnsureDefaults", EnsureDefaults)
PublishApi("LIDCOUNCIL_TouchLootProfileDomain", TouchLootProfileDomain)
PublishApi("LIDCOUNCIL_GetLootProfileDomainRevision", GetLootProfileDomainRevision)
PublishApi("LIDCOUNCIL_GetLootProfileRevisionSummary", GetLootProfileRevisionSummary)
PublishApi("LIDCOUNCIL_CalculateStableChecksum", coreSystemHelpers.CalculateStableChecksum)
PublishApi("LIDCOUNCIL_BuildLootProfileCurrentSessionChecksumSource", coreSystemHelpers.BuildLootProfileCurrentSessionChecksumSource, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileLiveBoardSnapshotChecksumSource", coreSystemHelpers.BuildLootProfileLiveBoardSnapshotChecksumSource, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileLiveBoardChecksumSource", coreSystemHelpers.BuildLootProfileLiveBoardChecksumSource, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileSnapshotDiff", BuildLootProfileSnapshotDiff)
PublishApi("LIDCOUNCIL_ExportLootProfileDomainSnapshot", coreSystemHelpers.ExportLootProfileDomainSnapshot, { global = false })
PublishApi("LIDCOUNCIL_ImportLootProfileDomainSnapshot", coreSystemHelpers.ImportLootProfileDomainSnapshot, { global = false })
PublishApi("LIDCOUNCIL_PreviewLootProfileDomainSnapshot", coreSystemHelpers.PreviewLootProfileDomainSnapshot, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileDomainSnapshotDiff", BuildLootProfileDomainSnapshotDiff, { global = false })
PublishApi("LIDCOUNCIL_ExportLootProfileSubdomainSnapshot", coreSystemHelpers.ExportLootProfileSubdomainSnapshot, { global = false })
PublishApi("LIDCOUNCIL_ImportLootProfileSubdomainSnapshot", coreSystemHelpers.ImportLootProfileSubdomainSnapshot, { global = false })
PublishApi("LIDCOUNCIL_PreviewLootProfileSubdomainSnapshot", coreSystemHelpers.PreviewLootProfileSubdomainSnapshot, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileSubdomainSnapshotDiff", BuildLootProfileSubdomainSnapshotDiff, { global = false })
PublishApi("LIDCOUNCIL_ExportLootProfileSubdomainPatch", coreSystemHelpers.ExportLootProfileSubdomainPatch, { global = false })
PublishApi("LIDCOUNCIL_ImportLootProfileSubdomainPatch", coreSystemHelpers.ImportLootProfileSubdomainPatch, { global = false })
PublishApi("LIDCOUNCIL_PreviewLootProfileSubdomainPatch", coreSystemHelpers.PreviewLootProfileSubdomainPatch, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileSubdomainPatchDiff", BuildLootProfileSubdomainPatchDiff, { global = false })
PublishApi("LIDCOUNCIL_ExportLootProfileConflictRowPatch", coreSystemHelpers.ExportLootProfileConflictRowPatch, { global = false })
PublishApi("LIDCOUNCIL_ImportLootProfileConflictRowPatch", coreSystemHelpers.ImportLootProfileConflictRowPatch, { global = false })
PublishApi("LIDCOUNCIL_PreviewLootProfileConflictRowPatch", coreSystemHelpers.PreviewLootProfileConflictRowPatch, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileConflictRowPatchDiff", coreSystemHelpers.BuildLootProfileConflictRowPatchDiff, { global = false })
PublishApi("LIDCOUNCIL_BuildLootProfileTransferConflictPreview", BuildLootProfileTransferConflictPreview, { global = false })
PublishApi("LIDCOUNCIL_ApplyLootProfileTransferConflictResolution", ApplyLootProfileTransferConflictResolution, { global = false })
PublishApi("LIDCOUNCIL_ExportLootProfileSubdomainPatchManifest", coreSystemHelpers.ExportLootProfileSubdomainPatchManifest, { global = false })
PublishApi("LIDCOUNCIL_PreviewLootProfileSubdomainPatchManifest", coreSystemHelpers.PreviewLootProfileSubdomainPatchManifest, { global = false })
PublishApi("LIDCOUNCIL_GetLootProfileSubchecksumManifest", GetLootProfileSubchecksumManifest, { global = false })
PublishApi("LIDCOUNCIL_NormalizeLootProfileSubdomainList", NormalizeLootProfileSubdomainList, { global = false })
PublishApi("LIDCOUNCIL_CloneLootProfileSubdomainList", CloneLootProfileSubdomainList, { global = false })
PublishApi("LIDCOUNCIL_FilterLootProfilePatchCapableSubdomains", FilterLootProfilePatchCapableSubdomains, { global = false })
PublishApi("LIDCOUNCIL_GetLootProfileSubdomainKeysForDomain", GetLootProfileSubdomainKeysForDomain, { global = false })
PublishApi("LIDCOUNCIL_GetLootProfileSubdomainLabel", GetLootProfileSubdomainLabel, { global = false })
PublishApi("LIDCOUNCIL_GetLootProfileSubdomainParentDomain", GetLootProfileSubdomainParentDomain, { global = false })
PublishApi("LIDCOUNCIL_ResolveLootProfileDomainsForSubdomains", ResolveLootProfileDomainsForSubdomains, { global = false })
PublishApi("LIDCOUNCIL_GetLootProfile", GetLootProfile)
PublishApi("LIDCOUNCIL_GetLootProfileNames", GetLootProfileNames)
PublishApi("LIDCOUNCIL_GetLootProfileId", GetLootProfileId)
PublishApi("LIDCOUNCIL_GetLootProfileIdentityInfo", GetLootProfileIdentityInfo, { global = false })
PublishApi("LIDCOUNCIL_FindLootProfileNameById", FindLootProfileNameById)
PublishApi("LIDCOUNCIL_GetLootProfileRole", GetLootProfileRole)
PublishApi("LIDCOUNCIL_GetLootProfileMaster", GetLootProfileMaster)
PublishApi("LIDCOUNCIL_GetLootProfileRoleEntries", GetLootProfileRoleEntries)
PublishApi("LIDCOUNCIL_GetLootProfileAccessInfo", GetLootProfileAccessInfo)
PublishApi("LIDCOUNCIL_GetLootProfileAccessInfoForRole", GetLootProfileAccessInfoForRole)
PublishApi("LIDCOUNCIL_GetLootProfilePermissionPolicyRoleState", GetLootProfilePermissionPolicyRoleState)
PublishApi("LIDCOUNCIL_GetLootProfilePermissionPolicyDiagnostics", GetLootProfilePermissionPolicyDiagnostics)
PublishApi("LIDCOUNCIL_GetSimulationRoleOverride", GetSimulationRoleOverride)
PublishApi("LIDCOUNCIL_GetSimulationRoleMode", GetSimulationRoleMode)
PublishApi("LIDCOUNCIL_SetCurrentLootProfile", SetCurrentLootProfile)
PublishApi("LIDCOUNCIL_GetEnabledLootProfileName", GetEnabledLootProfileName)
PublishApi("LIDCOUNCIL_GetPrimaryLootProfileName", GetPrimaryLootProfileName)
PublishApi("LIDCOUNCIL_CreateLootProfile", CreateLootProfile)
PublishApi("LIDCOUNCIL_DeleteLootProfile", DeleteLootProfile)
PublishApi("LIDCOUNCIL_CopyLootProfile", CopyLootProfile)
PublishApi("LIDCOUNCIL_ResetSimulationProfileContent", ResetSimulationProfileContent, { global = false })
PublishApi("LIDCOUNCIL_RepairSimulationProfileOwnership", RepairSimulationProfileOwnership, { global = false })
PublishApi("LIDCOUNCIL_SetLootProfileMemberRole", SetLootProfileMemberRole)
PublishApi("LIDCOUNCIL_TransferLootProfileMaster", TransferLootProfileMaster)
PublishApi("LIDCOUNCIL_SetSimulationRoleOverride", SetSimulationRoleOverride)
PublishApi("LIDCOUNCIL_SyncLootProfilePermissionState", SyncLootProfilePermissionState)
PublishApi("LIDCOUNCIL_EnsureLootProfileMemberRole", EnsureLootProfileMemberRole)
PublishApi("LIDCOUNCIL_SetLootProfilePermissionPolicyValue", SetLootProfilePermissionPolicyValue)
PublishApi("LIDCOUNCIL_ResetLootProfilePermissionPolicy", ResetLootProfilePermissionPolicy)
PublishApi("LIDCOUNCIL_SetLootProfileEnabled", SetLootProfileEnabled)
PublishApi("LIDCOUNCIL_SetLootProfileRaid", SetLootProfileRaid)
PublishApi("LIDCOUNCIL_ApplyLootRollTemplate", ApplyLootRollTemplate)
PublishApi("LIDCOUNCIL_AddLootRollOption", AddLootRollOption)
PublishApi("LIDCOUNCIL_UpdateLootRollOption", UpdateLootRollOption)
PublishApi("LIDCOUNCIL_MoveLootRollOption", MoveLootRollOption)
PublishApi("LIDCOUNCIL_RemoveLootRollOption", RemoveLootRollOption)
PublishApi("LIDCOUNCIL_UpdateLootPointMode", UpdateLootPointMode)
PublishApi("LIDCOUNCIL_UpdateLootLidTableRow", UpdateLootLidTableRow)
PublishApi("LIDCOUNCIL_AddLootPointRule", AddLootPointRule)
PublishApi("LIDCOUNCIL_RemoveLootPointRule", RemoveLootPointRule)
PublishApi("LIDCOUNCIL_ApplyIncomingLootProfileDelta", profileSyncSupportHelpers.ApplyIncomingLootProfileDelta, { global = false })
PublishApi("LIDCOUNCIL_GetLocalAutoPassEnabled", GetLocalAutoPassEnabled)
PublishApi("LIDCOUNCIL_SetLocalAutoPassEnabled", SetLocalAutoPassEnabled)
PublishApi("LIDCOUNCIL_AddLootLedgerEntry", AddLootLedgerEntry)
PublishApi("LIDCOUNCIL_RemoveLootLedgerEntry", RemoveLootLedgerEntry)
PublishApi("LIDCOUNCIL_ResolveLootPointValue", ResolveLootPointValue)
PublishApi("LIDCOUNCIL_AwardLootToCharacter", AwardLootToCharacter)
PublishApi("LIDCOUNCIL_GetLootLedgerCharacters", GetLootLedgerCharacters)
PublishApi("LIDCOUNCIL_StartLootSession", StartLootSession)
PublishApi("LIDCOUNCIL_ActivateLootSession", ActivateLootSession)
PublishApi("LIDCOUNCIL_GetActiveLootSession", GetActiveLootSession)
PublishApi("LIDCOUNCIL_StopLootSession", StopLootSession)
PublishApi("LIDCOUNCIL_AddLootSessionMember", AddLootSessionMember)
PublishApi("LIDCOUNCIL_GetLootSession", GetLootSession)
PublishApi("LIDCOUNCIL_JoinLootSession", JoinLootSession)
PublishApi("LIDCOUNCIL_UpsertLootSessionMember", UpsertLootSessionMember)
PublishApi("LIDCOUNCIL_RemoveLootSessionMember", RemoveLootSessionMember)
PublishApi("LIDCOUNCIL_FinishLootSession", FinishLootSession)
