local API = rawget(_G, "LIDCOUNCIL_API")
local INTERNAL_API = type(API) == "table" and type(API.__internal) == "table" and API.__internal or nil
if type(INTERNAL_API) ~= "table" then
  error("LidCouncil missing internal API registry.")
end

local RECOMMENDATION_HYDRATION = {
  maxRefreshes = 8,
  maxItemRequests = 500,
  refreshDelaySeconds = 0.15,
}

function RECOMMENDATION_HYDRATION.IsEffectivelyVisible(frame)
  if type(frame) ~= "table" then
    return false
  end
  if type(frame.IsVisible) == "function" then
    local ok, visible = pcall(frame.IsVisible, frame)
    if ok then
      return visible == true
    end
  end
  if type(frame.IsShown) == "function" then
    local ok, shown = pcall(frame.IsShown, frame)
    if ok then
      return shown == true
    end
  end
  return false
end

function RECOMMENDATION_HYDRATION.IsActive(editor, frame)
  if not RECOMMENDATION_HYDRATION.IsEffectivelyVisible(frame) then
    return false
  end
  if frame.__recommendationEmbedded == true
    and tostring(type(editor) == "table" and editor.currentPage or "") ~= "LootPresets" then
    return false
  end
  return true
end

function RECOMMENDATION_HYDRATION.Disarm(frame)
  if type(frame) ~= "table" then
    return
  end
  frame.__recommendationHydrationGeneration = (tonumber(frame.__recommendationHydrationGeneration) or 0) + 1
  frame.__recommendationHydrationKey = nil
  frame.__recommendationHydrationRefreshPending = nil
  frame.__recommendationHydrationRefreshCount = 0
  frame.__recommendationHydrationExhausted = nil
  frame.__recommendationJournalDataPending = false
  frame.__recommendationPendingItemIDs = {}
  frame.__recommendationRequestedItemIDs = {}
end

function RECOMMENDATION_HYDRATION.BeginCycle(frame, cycleKey)
  if type(frame) ~= "table" then
    return false
  end
  local resolvedKey = tostring(cycleKey or "")
  if resolvedKey ~= "" and frame.__recommendationHydrationKey == resolvedKey then
    return false
  end
  frame.__recommendationHydrationGeneration = (tonumber(frame.__recommendationHydrationGeneration) or 0) + 1
  frame.__recommendationHydrationKey = resolvedKey
  frame.__recommendationHydrationRefreshPending = nil
  frame.__recommendationHydrationRefreshCount = 0
  frame.__recommendationHydrationExhausted = nil
  frame.__recommendationJournalDataPending = false
  frame.__recommendationPendingItemIDs = {}
  frame.__recommendationRequestedItemIDs = {}
  return true
end

function RECOMMENDATION_HYDRATION.UpdatePendingItems(frame, items, journalDataPending, requestItemData)
  if type(frame) ~= "table" then
    return 0
  end
  frame.__recommendationJournalDataPending = journalDataPending == true
  frame.__recommendationPendingItemIDs = {}
  frame.__recommendationRequestedItemIDs = type(frame.__recommendationRequestedItemIDs) == "table"
    and frame.__recommendationRequestedItemIDs or {}
  if frame.__recommendationJournalDataPending ~= true then
    frame.__recommendationHydrationExhausted = nil
    return 0
  end

  local requested = 0
  local requestLimit = math.max(1, tonumber(RECOMMENDATION_HYDRATION.maxItemRequests) or 1)
  for _, pendingItem in ipairs(type(items) == "table" and items or {}) do
    local itemID = pendingItem.journalDataPending == true and tonumber(pendingItem.itemID) or nil
    if itemID then
      frame.__recommendationPendingItemIDs[itemID] = true
      if type(requestItemData) == "function"
        and frame.__recommendationRequestedItemIDs[itemID] ~= true
        and requested < requestLimit then
        frame.__recommendationRequestedItemIDs[itemID] = true
        requested = requested + 1
        pcall(requestItemData, itemID)
      end
    end
  end
  return requested
end

function RECOMMENDATION_HYDRATION.TryBeginRefresh(frame)
  if type(frame) ~= "table" or frame.__recommendationJournalDataPending ~= true then
    return false
  end
  local refreshCount = math.max(0, tonumber(frame.__recommendationHydrationRefreshCount) or 0)
  local refreshLimit = math.max(1, tonumber(RECOMMENDATION_HYDRATION.maxRefreshes) or 1)
  if refreshCount >= refreshLimit then
    frame.__recommendationHydrationExhausted = true
    return false
  end
  frame.__recommendationHydrationRefreshCount = refreshCount + 1
  return true
end

local function RunLootPresetsHydrationLifecycleValidation()
  local checks = {}
  local hiddenFrame = {
    __recommendationEmbedded = true,
    IsShown = function() return true end,
    IsVisible = function() return false end,
  }
  local visibleFrame = {
    __recommendationEmbedded = true,
    IsShown = function() return true end,
    IsVisible = function() return true end,
  }
  checks[#checks + 1] = RECOMMENDATION_HYDRATION.IsActive({ currentPage = "LootPresets" }, hiddenFrame) == false
  checks[#checks + 1] = RECOMMENDATION_HYDRATION.IsActive({ currentPage = "Profiles" }, visibleFrame) == false
  checks[#checks + 1] = RECOMMENDATION_HYDRATION.IsActive({ currentPage = "LootPresets" }, visibleFrame) == true

  RECOMMENDATION_HYDRATION.BeginCycle(visibleFrame, "profile:raid:normal")
  local requestCount = 0
  local pendingItems = {
    { itemID = 101, journalDataPending = true },
    { itemID = 101, journalDataPending = true },
    { itemID = 202, journalDataPending = true },
  }
  RECOMMENDATION_HYDRATION.UpdatePendingItems(visibleFrame, pendingItems, true, function()
    requestCount = requestCount + 1
  end)
  RECOMMENDATION_HYDRATION.UpdatePendingItems(visibleFrame, pendingItems, true, function()
    requestCount = requestCount + 1
  end)
  checks[#checks + 1] = requestCount == 2

  local bounded = true
  for _ = 1, RECOMMENDATION_HYDRATION.maxRefreshes do
    bounded = bounded and RECOMMENDATION_HYDRATION.TryBeginRefresh(visibleFrame) == true
  end
  checks[#checks + 1] = bounded and RECOMMENDATION_HYDRATION.TryBeginRefresh(visibleFrame) == false

  local previousGeneration = tonumber(visibleFrame.__recommendationHydrationGeneration) or 0
  RECOMMENDATION_HYDRATION.Disarm(visibleFrame)
  checks[#checks + 1] = visibleFrame.__recommendationJournalDataPending == false
    and visibleFrame.__recommendationHydrationKey == nil
    and (tonumber(visibleFrame.__recommendationHydrationGeneration) or 0) > previousGeneration

  local rearmedRequests = 0
  RECOMMENDATION_HYDRATION.BeginCycle(visibleFrame, "profile:raid:normal")
  RECOMMENDATION_HYDRATION.UpdatePendingItems(visibleFrame, pendingItems, true, function()
    rearmedRequests = rearmedRequests + 1
  end)
  checks[#checks + 1] = rearmedRequests == 2
    and RECOMMENDATION_HYDRATION.TryBeginRefresh(visibleFrame) == true

  local passed = 0
  for _, checkPassed in ipairs(checks) do
    if checkPassed then
      passed = passed + 1
    end
  end
  return passed == #checks, {
    total = #checks,
    passed = passed,
    failed = #checks - passed,
  }
end

INTERNAL_API.LIDCOUNCIL_RunLootPresetsHydrationLifecycleValidation_Impl = RunLootPresetsHydrationLifecycleValidation

INTERNAL_API.LIDCOUNCIL_CreateLootListRecommendationsViewHelpers_Impl = function(deps)
  deps = type(deps) == "table" and deps or {}

  local ApplyFrameSkin = deps.ApplyFrameSkin
  local ApplyLootListToolbarButtonStyle = deps.ApplyLootListToolbarButtonStyle
  local BindMouseWheelScroll = deps.BindMouseWheelScroll
  local CreateCustomDropdownControl = deps.CreateCustomDropdownControl
  local CollectRaidEntries = deps.CollectRaidEntries
  local CollectRaidEncounterEntries = deps.CollectRaidEncounterEntries
  local FindRaidEntryByProfile = deps.FindRaidEntryByProfile
  local CollectRaidLootEntries = deps.CollectRaidLootEntries
  local RAID_LOOT_DIFFICULTIES = deps.RAID_LOOT_DIFFICULTIES
  local FormatRaidLootItemLabel = deps.FormatRaidLootItemLabel
  local ResolveRollOptionIcon = deps.ResolveRollOptionIcon
  local GetLootProfile = deps.GetLootProfile
  local GetCharacterKey = deps.GetCharacterKey
  local TrimText = deps.TrimText
  local ResolveOptionalApiFunction = deps.ResolveOptionalApiFunction

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

  local function ResolveApiFunction(name)
    if type(ResolveOptionalApiFunction) == "function" then
      local fn = ResolveOptionalApiFunction(name)
      if type(fn) == "function" then
        return fn
      end
    end
    local api = rawget(_G, "LIDCOUNCIL_API")
    if type(api) == "table" and type(api[name]) == "function" then
      return api[name]
    end
    return rawget(_G, name)
  end

  local function GetRollOptions(profile)
    local options = type(profile) == "table" and type(profile.settings) == "table" and profile.settings.rollOptions or nil
    return type(options) == "table" and options or {}
  end

  local function ResolveRecommendation(profileName, item, options, recommendationOptions)
    local resolver = ResolveApiFunction("LIDCOUNCIL_ResolvePersonalRecommendation")
    if type(resolver) ~= "function" then
      return nil, nil, "NONE"
    end
    return resolver(profileName, item and item.itemID, item and item.link, item and item.name, options, recommendationOptions)
  end

  local function SetRecommendation(profileName, item, priority, option, recommendationOptions)
    local setter = ResolveApiFunction("LIDCOUNCIL_SetPersonalRecommendation")
    if type(setter) ~= "function" then
      return false
    end
    return setter(profileName, item and item.itemID, item and item.link, item and item.name, priority, option, recommendationOptions)
  end

  local function ClearRecommendation(profileName, item, recommendationOptions)
    local clearer = ResolveApiFunction("LIDCOUNCIL_ClearPersonalRecommendation")
    if type(clearer) ~= "function" then
      return false
    end
    return clearer(profileName, item and item.itemID, item and item.link, item and item.name, recommendationOptions)
  end

  local function FormatOptionText(priority, option)
    local label = trim(type(option) == "table" and (option.label or option.key) or "")
    if label == "" then
      label = "Roll Option"
    end
    return "#" .. tostring(priority) .. " " .. label
  end

  local function SetDropdownValue(dropdown, value, text)
    if not dropdown then
      return
    end
    if dropdown.SetSelectedValue then
      dropdown:SetSelectedValue(value)
    end
    if dropdown.SetText then
      dropdown:SetText(text)
    end
  end

  local function GetDifficultyEntries()
    local entries = type(RAID_LOOT_DIFFICULTIES) == "table" and RAID_LOOT_DIFFICULTIES or nil
    if type(entries) == "table" and #entries > 0 then
      return entries
    end
    return {
      { id = 14, name = "Normal" },
      { id = 15, name = "Heroic" },
      { id = 16, name = "Mythic" },
    }
  end

  local function GetDifficultyName(difficultyID)
    local resolvedDifficultyID = tonumber(difficultyID)
    for _, entry in ipairs(GetDifficultyEntries()) do
      if tonumber(entry.id) == resolvedDifficultyID then
        return tostring(entry.name or resolvedDifficultyID)
      end
    end
    return "Normal"
  end

  local FILTER_DROPDOWN_THEME = {
    dropdownButtonBg = { 0.07, 0.10, 0.14, 0.98 },
    dropdownPopupBg = { 0.045, 0.06, 0.08, 0.99 },
    border = { 0.34, 0.41, 0.50, 0.95 },
    active = { 0.35, 0.62, 0.93, 0.90 },
    text = { 0.92, 0.94, 0.97, 1.00 },
  }

  local function GetRecommendationCharacterKeys()
    local getter = ResolveApiFunction("LIDCOUNCIL_GetPersonalRecommendationCharacterKeys")
    local keys, currentKey = nil, nil
    if type(getter) == "function" then
      keys, currentKey = getter()
    end
    local resolvedCurrentKey = trim(currentKey)
    if resolvedCurrentKey == "" then
      resolvedCurrentKey = trim(type(GetCharacterKey) == "function" and GetCharacterKey() or "")
    end
    local result = {}
    local seen = {}
    for _, value in ipairs(type(keys) == "table" and keys or {}) do
      local key = trim(value)
      if key ~= "" and not seen[key] then
        seen[key] = true
        result[#result + 1] = key
      end
    end
    if resolvedCurrentKey ~= "" and not seen[resolvedCurrentKey] then
      result[#result + 1] = resolvedCurrentKey
    end
    table.sort(result, function(left, right)
      return string.lower(left) < string.lower(right)
    end)
    return result, resolvedCurrentKey
  end

  local function ApplyDropdownTheme(dropdown, theme, accentColor)
    if not dropdown then
      return
    end
    dropdown.__lid_themeOverride = theme
    if dropdown.button then
      local accent = dropdown.__recommendationAccent
      if not accent then
        accent = dropdown.button:CreateTexture(nil, "OVERLAY")
        accent:SetTexture("Interface\\Buttons\\WHITE8x8")
        accent:SetPoint("TOPLEFT", dropdown.button, "TOPLEFT", 1, -1)
        accent:SetPoint("BOTTOMLEFT", dropdown.button, "BOTTOMLEFT", 1, 1)
        accent:SetWidth(3)
        dropdown.__recommendationAccent = accent
      end
      if type(accentColor) == "table" then
        accent:SetVertexColor(accentColor[1] or 0.70, accentColor[2] or 0.72, accentColor[3] or 0.76, 0.95)
        accent:Show()
      else
        accent:Hide()
      end
    end
    if dropdown.ApplyTheme then
      dropdown:ApplyTheme()
    end
  end

  local function ApplyFilterDropdownTheme(dropdown)
    ApplyDropdownTheme(dropdown, FILTER_DROPDOWN_THEME, nil)
  end

  local function ResolveOptionColor(option, fallback)
    local color = type(option) == "table" and option.color or nil
    if type(color) ~= "table" then
      return fallback
    end
    return {
      tonumber(color[1]) or fallback[1],
      tonumber(color[2]) or fallback[2],
      tonumber(color[3]) or fallback[3],
    }
  end

  local function BrightenColor(color)
    return {
      math.min(1, (tonumber(color[1]) or 0.72) * 0.68 + 0.28),
      math.min(1, (tonumber(color[2]) or 0.72) * 0.68 + 0.28),
      math.min(1, (tonumber(color[3]) or 0.76) * 0.68 + 0.28),
    }
  end

  local function ApplyRecommendationDropdownTheme(dropdown, option, status)
    local fallback = status == "MISSING" and { 1.00, 0.38, 0.28 } or { 0.62, 0.66, 0.73 }
    local accent = ResolveOptionColor(option, fallback)
    local textColor = BrightenColor(accent)
    ApplyDropdownTheme(dropdown, {
      dropdownButtonBg = { 0.045, 0.06, 0.08, 0.99 },
      dropdownPopupBg = { 0.035, 0.045, 0.06, 0.99 },
      border = { math.max(0.24, accent[1] * 0.72), math.max(0.24, accent[2] * 0.72), math.max(0.24, accent[3] * 0.72), 0.95 },
      active = accent,
      text = { textColor[1], textColor[2], textColor[3], 1.00 },
    }, accent)
  end

  local function BuildRecommendationOptions(frame, item)
    if not (frame and type(item) == "table") then
      return nil
    end
    local options = {
      specID = tonumber(frame.__recommendationFilterSpecID) or 0,
      characterKey = trim(frame.__recommendationTargetCharacterKey),
    }
    if frame.__recommendationPerDifficulty ~= true then
      return options
    end
    local difficultyID = tonumber(item.difficultyID)
    if not difficultyID then
      return options
    end
    options.perDifficulty = true
    options.exactDifficulty = true
    options.difficultyID = difficultyID
    options.difficultyName = trim(item.difficultyName ~= nil and item.difficultyName or GetDifficultyName(difficultyID))
    return options
  end

  local function FormatRecommendationScope(frame, item)
    if not (frame and frame.__recommendationPerDifficulty == true) then
      return ""
    end
    local difficultyName = trim(type(item) == "table" and item.difficultyName or "")
    if difficultyName == "" then
      difficultyName = GetDifficultyName(type(item) == "table" and item.difficultyID or nil)
    end
    return " for " .. difficultyName
  end

  local function GetRaidTierEntries()
    local raids = type(CollectRaidEntries) == "function" and CollectRaidEntries() or {}
    local tiers = {}
    local seen = {}
    for _, raid in ipairs(raids) do
      local tier = tonumber(raid.tier)
      if tier and not seen[tier] then
        seen[tier] = true
        tiers[#tiers + 1] = {
          tier = tier,
          tierName = tostring(raid.tierName or ("Season " .. tostring(tier))),
        }
      end
    end
    table.sort(tiers, function(a, b)
      return (tonumber(a.tier) or 0) > (tonumber(b.tier) or 0)
    end)
    return tiers
  end

  local function GetCurrentRaidTierValue()
    local tiers = GetRaidTierEntries()
    if tiers[1] then
      return tonumber(tiers[1].tier)
    end
    return type(EJ_GetCurrentTier) == "function" and tonumber(EJ_GetCurrentTier()) or nil
  end

  local function GetProfileSeasonSnapshot(profile)
    local snapshot = type(profile) == "table"
      and type(profile.settings) == "table"
      and type(profile.settings.seasonSnapshot) == "table"
      and profile.settings.seasonSnapshot
      or nil
    if type(snapshot) ~= "table" or not tonumber(snapshot.tier) or type(snapshot.raids) ~= "table" or #snapshot.raids == 0 then
      return nil
    end
    return snapshot
  end

  local function CollectCurrentSeasonRaids(profile)
    local snapshot = GetProfileSeasonSnapshot(profile)
    if snapshot then
      local raids = {}
      local tier = tonumber(snapshot.tier)
      local tierName = trim(snapshot.tierName) ~= "" and tostring(snapshot.tierName) or "Profile Season"
      for _, raid in ipairs(snapshot.raids or {}) do
        local instanceID = tonumber(raid.instanceID)
        if instanceID then
          raids[#raids + 1] = {
            instanceID = instanceID,
            name = tostring(raid.name or instanceID),
            tier = tier,
            tierName = tierName,
            frozen = true,
          }
        end
      end
      table.sort(raids, function(a, b)
        return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
      end)
      return raids
    end

    local targetTier = GetCurrentRaidTierValue()
    local raids = {}
    for _, raid in ipairs(type(CollectRaidEntries) == "function" and CollectRaidEntries() or {}) do
      if tonumber(raid.tier) == tonumber(targetTier) then
        raids[#raids + 1] = raid
      end
    end
    table.sort(raids, function(a, b)
      return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
    end)
    return raids
  end

  local function FindRaidByInstanceID(raids, instanceID)
    local resolvedInstanceID = tonumber(instanceID)
    if not resolvedInstanceID then
      return nil
    end
    for _, raid in ipairs(type(raids) == "table" and raids or {}) do
      if tonumber(raid.instanceID) == resolvedInstanceID then
        return raid
      end
    end
    return nil
  end

  local function FindEncounterByID(encounters, encounterID)
    local resolvedEncounterID = tonumber(encounterID)
    if not resolvedEncounterID then
      return nil
    end
    for _, encounter in ipairs(type(encounters) == "table" and encounters or {}) do
      if tonumber(encounter.encounterID) == resolvedEncounterID then
        return encounter
      end
    end
    return nil
  end

  local function BuildSpecFilterEntries()
    local entries = {
      {
        classID = 0,
        specID = 0,
        text = "All Classes / Specs",
      },
    }
    local seen = {}
    if type(GetNumClasses) == "function"
      and type(GetClassInfo) == "function"
      and type(GetNumSpecializationsForClassID) == "function"
      and type(GetSpecializationInfoForClassID) == "function" then
      local classCount = tonumber(GetNumClasses()) or 0
      for classID = 1, classCount do
        local className = select(1, GetClassInfo(classID))
        if className then
          entries[#entries + 1] = {
            classID = classID,
            specID = 0,
            text = tostring(className) .. " - All Specs",
          }
        end
        local specCount = tonumber(GetNumSpecializationsForClassID(classID)) or 0
        for specIndex = 1, specCount do
          local specID, specName, _, icon, role = GetSpecializationInfoForClassID(classID, specIndex)
          if specID and specName and not seen[specID] then
            local text = tostring(className or "Class") .. " - " .. tostring(specName)
            if role then
              text = text .. " (" .. tostring(role) .. ")"
            end
            entries[#entries + 1] = {
              classID = classID,
              specID = tonumber(specID),
              text = text,
              icon = icon,
              iconSize = 16,
            }
            seen[specID] = true
          end
        end
      end
    end
    table.sort(entries, function(a, b)
      local aGlobal = (tonumber(a.classID) or 0) == 0 and (tonumber(a.specID) or 0) == 0
      local bGlobal = (tonumber(b.classID) or 0) == 0 and (tonumber(b.specID) or 0) == 0
      if aGlobal ~= bGlobal then
        return aGlobal
      end
      if (tonumber(a.specID) or 0) == 0 and (tonumber(b.specID) or 0) ~= 0 then
        return true
      elseif (tonumber(b.specID) or 0) == 0 and (tonumber(a.specID) or 0) ~= 0 then
        return false
      end
      return string.lower(tostring(a.text or "")) < string.lower(tostring(b.text or ""))
    end)
    return entries
  end

  local function GetCurrentSpecFilter()
    local currentSpecID
    if type(GetSpecialization) == "function" and type(GetSpecializationInfo) == "function" then
      local specIndex = GetSpecialization()
      if specIndex then
        local ok, specID = pcall(GetSpecializationInfo, specIndex)
        currentSpecID = ok and tonumber(specID) or nil
      end
    end
    if not currentSpecID then
      return 0, 0, "All Classes / Specs"
    end
    for _, entry in ipairs(BuildSpecFilterEntries()) do
      if tonumber(entry.specID) == currentSpecID then
        return tonumber(entry.classID) or 0, currentSpecID, tostring(entry.text or "Current Specialization")
      end
    end
    return 0, currentSpecID, "Current Specialization"
  end

  local function FindSpecFilterEntry(classID, specID)
    local resolvedClassID = tonumber(classID) or 0
    local resolvedSpecID = tonumber(specID) or 0
    for _, entry in ipairs(BuildSpecFilterEntries()) do
      if tonumber(entry.classID) == resolvedClassID and tonumber(entry.specID) == resolvedSpecID then
        return entry
      end
    end
    if resolvedSpecID == 0 then
      return BuildSpecFilterEntries()[1]
    end
    return nil
  end

  local function RaisePopupControl(control, popup, offset)
    if not control or not popup then
      return
    end
    local level = ((popup.GetFrameLevel and popup:GetFrameLevel()) or 1) + (tonumber(offset) or 10)
    local strata = (popup.GetFrameStrata and popup:GetFrameStrata()) or "FULLSCREEN_DIALOG"
    if control.SetFrameStrata then
      control:SetFrameStrata(strata)
    end
    if control.SetFrameLevel then
      control:SetFrameLevel(level)
    end
    if control.GetChildren then
      local children = { control:GetChildren() }
      for _, child in ipairs(children) do
        if child.SetFrameStrata then
          child:SetFrameStrata(strata)
        end
        if child.SetFrameLevel then
          child:SetFrameLevel(level + 1)
        end
      end
    end
  end

  local function HideItemTooltip()
    if type(GameTooltip) == "table" then
      GameTooltip:Hide()
    end
  end

  local function ShowItemTooltip(self)
    if type(GameTooltip) ~= "table" then
      return
    end
    local item = type(self) == "table" and self.__recommendationItem or nil
    if type(item) ~= "table" then
      return
    end
    local itemLink = trim(item.link)
    local itemID = tonumber(item.itemID)
    local itemName = trim(item.name)
    GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
    if item.journalDescriptorReady == true and itemLink ~= "" and GameTooltip.SetHyperlink then
      local ok = pcall(GameTooltip.SetHyperlink, GameTooltip, itemLink)
      if not ok then
        GameTooltip:SetText(itemName ~= "" and itemName or ("Item #" .. tostring(itemID or "?")), 1, 0.82, 0.28, 1, true)
        GameTooltip:AddLine("Loading the exact difficulty item data...", 0.30, 0.70, 1.00, true)
      end
    else
      GameTooltip:SetText(itemName ~= "" and itemName or ("Item #" .. tostring(itemID or "?")), 1, 0.82, 0.28, 1, true)
      GameTooltip:AddLine("Loading the exact difficulty item data...", 0.30, 0.70, 1.00, true)
      if itemID and type(C_Item) == "table" and type(C_Item.RequestLoadItemDataByID) == "function" then
        pcall(C_Item.RequestLoadItemDataByID, itemID)
      end
    end
    if trim(item.encounterName) ~= "" then
      GameTooltip:AddLine("Boss: " .. tostring(item.encounterName), 0.82, 0.82, 0.90, true)
    end
    if trim(item.difficultyName) ~= "" then
      GameTooltip:AddLine("Difficulty: " .. tostring(item.difficultyName), 0.82, 0.82, 0.90, true)
    end
    GameTooltip:Show()
  end

  local function RegisterRecommendationHydrationEvents(frame)
    if not frame or frame.__recommendationHydrationEventsRegistered then
      return
    end
    frame.__recommendationHydrationEventsRegistered = true
    for _, eventName in ipairs({ "EJ_LOOT_DATA_RECIEVED", "GET_ITEM_INFO_RECEIVED", "ITEM_DATA_LOAD_RESULT" }) do
      pcall(frame.RegisterEvent, frame, eventName)
    end
    frame:HookScript("OnEvent", function(self, _, itemID)
      if self.__recommendationJournalDataPending ~= true then
        return
      end
      if not RECOMMENDATION_HYDRATION.IsActive(editor, self) then
        RECOMMENDATION_HYDRATION.Disarm(self)
        return
      end
      local resolvedItemID = tonumber(itemID)
      if resolvedItemID and type(self.__recommendationPendingItemIDs) == "table"
        and self.__recommendationPendingItemIDs[resolvedItemID] ~= true then
        return
      end
      if self.__recommendationHydrationRefreshPending then
        return
      end
      local refreshLimit = math.max(1, tonumber(RECOMMENDATION_HYDRATION.maxRefreshes) or 1)
      if (tonumber(self.__recommendationHydrationRefreshCount) or 0) >= refreshLimit then
        self.__recommendationHydrationExhausted = true
        return
      end
      self.__recommendationHydrationRefreshPending = true
      if type(C_Timer) == "table" and type(C_Timer.After) == "function" then
        local generation = tonumber(self.__recommendationHydrationGeneration) or 0
        C_Timer.After(RECOMMENDATION_HYDRATION.refreshDelaySeconds, function()
          self.__recommendationHydrationRefreshPending = nil
          if (tonumber(self.__recommendationHydrationGeneration) or 0) == generation
            and RECOMMENDATION_HYDRATION.IsActive(editor, self)
            and RECOMMENDATION_HYDRATION.TryBeginRefresh(self)
            and type(self.__recommendationRefresh) == "function"
          then
            self.__recommendationRefresh()
          end
        end)
      else
        self.__recommendationHydrationRefreshPending = nil
      end
    end)
  end

  local function EnsureRecommendationPopup(editor)
    if editor and editor.lootLedgerRecommendationsPopupFrame then
      return editor.lootLedgerRecommendationsPopupFrame
    end
    if not editor then
      return nil
    end

    local frame = CreateFrame("Frame", "LIDCOUNCIL_LootListRecommendationsPopup", UIParent, "BackdropTemplate")
    frame:SetSize(860, 660)
    frame:SetPoint("CENTER", 0, 0)
    frame:SetFrameStrata("FULLSCREEN_DIALOG")
    if frame.SetFrameLevel then
      frame:SetFrameLevel(500)
    end
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:SetToplevel(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
    if type(ApplyFrameSkin) == "function" then
      ApplyFrameSkin(frame)
    end
    frame:SetScript("OnShow", function()
      if editor and editor.UpdateLootListModeButtons then
        editor.UpdateLootListModeButtons()
      end
    end)
    frame:SetScript("OnHide", function()
      RECOMMENDATION_HYDRATION.Disarm(frame)
      if editor and editor.UpdateLootListModeButtons then
        editor.UpdateLootListModeButtons()
      end
    end)

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 18, -17)
    title:SetText("Personal Recommendations")
    frame.popupTitle = title

    local closeBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate,BackdropTemplate")
    closeBtn:SetSize(78, 22)
    closeBtn:SetPoint("TOPRIGHT", -14, -14)
    closeBtn:SetText("Close")
    if type(ApplyLootListToolbarButtonStyle) == "function" then
      ApplyLootListToolbarButtonStyle(closeBtn)
    elseif type(ApplyFrameSkin) == "function" then
      ApplyFrameSkin(closeBtn)
    end
    closeBtn:SetScript("OnClick", function()
      frame:Hide()
    end)
    frame.closeBtn = closeBtn
    RaisePopupControl(closeBtn, frame, 40)

    local intro = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    intro:SetPoint("TOPLEFT", 18, -44)
    intro:SetWidth(800)
    intro:SetJustifyH("LEFT")
    intro:SetText("Local-only raid item reminders. Choose a profile-season raid, boss, and difficulty, then select the roll priority you expect to use.")
    frame.intro = intro

    local raidLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    raidLabel:SetPoint("TOPLEFT", 18, -77)
    raidLabel:SetText("Raid:")
    frame.raidLabel = raidLabel

    if type(CreateCustomDropdownControl) == "function" then
      local raidDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 240,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 12,
        text = "Select Raid",
      })
      raidDropdown:SetPoint("LEFT", raidLabel, "RIGHT", 8, 0)
      frame.raidDropdown = raidDropdown
      RaisePopupControl(raidDropdown, frame, 35)
    end

    local bossLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    bossLabel:SetPoint("TOPLEFT", 306, -77)
    bossLabel:SetText("Boss:")
    frame.bossLabel = bossLabel

    if type(CreateCustomDropdownControl) == "function" then
      local bossDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 238,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 12,
        text = "All Bosses",
      })
      bossDropdown:SetPoint("LEFT", bossLabel, "RIGHT", 8, 0)
      frame.bossDropdown = bossDropdown
      RaisePopupControl(bossDropdown, frame, 35)
    end

    local difficultyLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    difficultyLabel:SetPoint("TOPLEFT", 596, -77)
    difficultyLabel:SetText("Difficulty:")
    frame.difficultyLabel = difficultyLabel

    if type(CreateCustomDropdownControl) == "function" then
      local difficultyDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 120,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 6,
        text = "Normal",
      })
      difficultyDropdown:SetPoint("LEFT", difficultyLabel, "RIGHT", 8, 0)
      frame.difficultyDropdown = difficultyDropdown
      RaisePopupControl(difficultyDropdown, frame, 35)
    end

    local specFilterLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    specFilterLabel:SetPoint("TOPLEFT", 18, -107)
    specFilterLabel:SetText("Class / Spec:")
    frame.specFilterLabel = specFilterLabel

    if type(CreateCustomDropdownControl) == "function" then
      local specFilterDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 310,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 14,
        text = "All Classes / Specs",
      })
      specFilterDropdown:SetPoint("LEFT", specFilterLabel, "RIGHT", 8, 0)
      frame.specFilterDropdown = specFilterDropdown
      RaisePopupControl(specFilterDropdown, frame, 35)
    end

    local perDifficultyCheck = CreateFrame("CheckButton", nil, frame, "UICheckButtonTemplate")
    perDifficultyCheck:SetSize(24, 24)
    perDifficultyCheck:SetPoint("LEFT", frame.specFilterDropdown or specFilterLabel, "RIGHT", 18, 0)
    if perDifficultyCheck.Text then
      perDifficultyCheck.Text:SetText("Per Difficulty")
    else
      perDifficultyCheck.label = perDifficultyCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      perDifficultyCheck.label:SetPoint("LEFT", perDifficultyCheck, "RIGHT", 2, 0)
      perDifficultyCheck.label:SetText("Per Difficulty")
    end
    perDifficultyCheck:SetScript("OnClick", function(self)
      frame.__recommendationPerDifficulty = self:GetChecked() and true or false
      if type(frame.__recommendationRefresh) == "function" then
        frame.__recommendationRefresh()
      end
    end)
    frame.perDifficultyCheck = perDifficultyCheck
    RaisePopupControl(perDifficultyCheck, frame, 35)

    local scroll = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 14, -142)
    scroll:SetPoint("BOTTOMRIGHT", -32, 14)
    if scroll.SetFrameLevel then
      scroll:SetFrameLevel(((frame.GetFrameLevel and frame:GetFrameLevel()) or 1) + 5)
    end
    if type(BindMouseWheelScroll) == "function" then
      BindMouseWheelScroll(scroll, 20)
    end
    frame.scroll = scroll

    local content = CreateFrame("Frame", nil, scroll)
    content:SetSize(790, 1)
    if content.SetFrameLevel then
      content:SetFrameLevel(((scroll.GetFrameLevel and scroll:GetFrameLevel()) or 1) + 1)
    end
    scroll:SetScrollChild(content)
    frame.content = content

    local emptyText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    emptyText:SetPoint("CENTER", frame, "CENTER", 0, -10)
    emptyText:SetWidth(680)
    emptyText:SetJustifyH("CENTER")
    emptyText:SetText("No raid loot entries are available for the selected filters.")
    emptyText:Hide()
    frame.emptyText = emptyText

    editor.lootLedgerRecommendationsPopupFrame = frame
    RegisterRecommendationHydrationEvents(frame)
    frame:Hide()
    return frame
  end

  local function EnsureEmbeddedRecommendationView(editor, parent)
    if parent and parent.__recommendationEmbeddedViewBuilt then
      return parent
    end
    if not parent then
      return nil
    end

    local frame = parent
    frame.__recommendationEmbeddedViewBuilt = true
    frame.__recommendationEmbedded = true
    frame:HookScript("OnHide", function(self)
      RECOMMENDATION_HYDRATION.Disarm(self)
    end)
    frame:SetFrameStrata("DIALOG")
    frame:EnableMouse(true)

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 0, -4)
    title:SetText("Personal Roll Presets")
    frame.popupTitle = title

    local intro = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    intro:SetPoint("TOPLEFT", 0, -31)
    intro:SetPoint("TOPRIGHT", -24, -31)
    intro:SetJustifyH("LEFT")
    intro:SetTextColor(0.72, 0.76, 0.84, 1)
    frame.intro = intro

    local characterLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    characterLabel:SetPoint("TOPLEFT", 0, -55)
    characterLabel:SetText("Editing Character:")
    frame.characterLabel = characterLabel
    if type(CreateCustomDropdownControl) == "function" then
      local characterDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 280,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 12,
        text = "Current character",
      })
      characterDropdown:SetPoint("LEFT", characterLabel, "RIGHT", 8, 0)
      ApplyFilterDropdownTheme(characterDropdown)
      frame.characterDropdown = characterDropdown
      RaisePopupControl(characterDropdown, frame, 35)
    end

    local divider = frame:CreateTexture(nil, "ARTWORK")
    divider:SetTexture("Interface\\Buttons\\WHITE8x8")
    divider:SetPoint("TOPLEFT", 0, -84)
    divider:SetPoint("TOPRIGHT", -24, -84)
    divider:SetHeight(1)
    divider:SetVertexColor(0.45, 0.52, 0.68, 0.55)

    local raidLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    raidLabel:SetPoint("TOPLEFT", 0, -102)
    raidLabel:SetText("Raid:")
    frame.raidLabel = raidLabel
    if type(CreateCustomDropdownControl) == "function" then
      local raidDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 190,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 12,
        text = "Select Raid",
      })
      raidDropdown:SetPoint("LEFT", raidLabel, "RIGHT", 8, 0)
      ApplyFilterDropdownTheme(raidDropdown)
      frame.raidDropdown = raidDropdown
      RaisePopupControl(raidDropdown, frame, 35)
    end

    local bossLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    bossLabel:SetPoint("TOPLEFT", 245, -102)
    bossLabel:SetText("Boss:")
    frame.bossLabel = bossLabel
    if type(CreateCustomDropdownControl) == "function" then
      local bossDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 210,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 12,
        text = "All Bosses",
      })
      bossDropdown:SetPoint("LEFT", bossLabel, "RIGHT", 8, 0)
      ApplyFilterDropdownTheme(bossDropdown)
      frame.bossDropdown = bossDropdown
      RaisePopupControl(bossDropdown, frame, 35)
    end

    local specFilterLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    specFilterLabel:SetPoint("TOPLEFT", 510, -102)
    specFilterLabel:SetText("Class / Spec:")
    frame.specFilterLabel = specFilterLabel
    if type(CreateCustomDropdownControl) == "function" then
      local specFilterDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 280,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 14,
        text = "All Classes / Specs",
      })
      specFilterDropdown:SetPoint("LEFT", specFilterLabel, "RIGHT", 8, 0)
      ApplyFilterDropdownTheme(specFilterDropdown)
      frame.specFilterDropdown = specFilterDropdown
      RaisePopupControl(specFilterDropdown, frame, 35)
    end

    local difficultyLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    difficultyLabel:SetPoint("TOPLEFT", 0, -132)
    difficultyLabel:SetText("Difficulty:")
    frame.difficultyLabel = difficultyLabel
    if type(CreateCustomDropdownControl) == "function" then
      local difficultyDropdown = CreateCustomDropdownControl(editor, frame, {
        width = 140,
        buttonHeight = 24,
        rowHeight = 20,
        maxVisibleEntries = 6,
        text = "Normal",
      })
      difficultyDropdown:SetPoint("LEFT", difficultyLabel, "RIGHT", 8, 0)
      ApplyFilterDropdownTheme(difficultyDropdown)
      frame.difficultyDropdown = difficultyDropdown
      RaisePopupControl(difficultyDropdown, frame, 35)
    end

    local perDifficultyCheck = CreateFrame("CheckButton", nil, frame, "UICheckButtonTemplate")
    perDifficultyCheck:SetSize(24, 24)
    perDifficultyCheck:SetPoint("LEFT", frame.difficultyDropdown or difficultyLabel, "RIGHT", 16, 0)
    if perDifficultyCheck.Text then
      perDifficultyCheck.Text:SetText("Per Difficulty")
    else
      perDifficultyCheck.label = perDifficultyCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      perDifficultyCheck.label:SetPoint("LEFT", perDifficultyCheck, "RIGHT", 2, 0)
      perDifficultyCheck.label:SetText("Per Difficulty")
    end
    perDifficultyCheck:SetScript("OnClick", function(self)
      frame.__recommendationPerDifficulty = self:GetChecked() and true or false
      if type(frame.__recommendationRefresh) == "function" then
        frame.__recommendationRefresh()
      end
    end)
    frame.perDifficultyCheck = perDifficultyCheck
    RaisePopupControl(perDifficultyCheck, frame, 35)

    local scroll = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 0, -170)
    scroll:SetPoint("TOPRIGHT", -24, -170)
    scroll:SetPoint("BOTTOMLEFT", 0, 0)
    scroll:SetPoint("BOTTOMRIGHT", -24, 0)
    if type(BindMouseWheelScroll) == "function" then
      BindMouseWheelScroll(scroll, 20)
    end
    frame.scroll = scroll

    local content = CreateFrame("Frame", nil, scroll)
    content:SetSize(1, 1)
    scroll:SetScrollChild(content)
    frame.content = content

    local emptyText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    emptyText:SetPoint("CENTER", frame, "CENTER", 0, 20)
    emptyText:SetWidth(680)
    emptyText:SetJustifyH("CENTER")
    emptyText:SetText("No raid loot entries are available for the selected filters.")
    emptyText:Hide()
    frame.emptyText = emptyText

    frame:HookScript("OnSizeChanged", function(self)
      if type(self.__recommendationRefresh) == "function" then
        self.__recommendationRefresh()
      end
    end)
    RegisterRecommendationHydrationEvents(frame)
    return frame
  end

  local function EnsureRows(frame, count)
    frame.recommendationRows = frame.recommendationRows or {}
    while #frame.recommendationRows < count do
      local row = CreateFrame("Frame", nil, frame.content, "BackdropTemplate")
      row:SetSize(770, 56)
      if type(ApplyFrameSkin) == "function" then
        ApplyFrameSkin(row)
      end
      if row.SetBackdropColor then
        row:SetBackdropColor(0.008, 0.012, 0.021, 0.97)
      end
      if row.SetBackdropBorderColor then
        row:SetBackdropBorderColor(0.18, 0.22, 0.31, 0.88)
      end
      if row.SetFrameLevel and frame.content and frame.content.GetFrameLevel then
        row:SetFrameLevel((frame.content:GetFrameLevel() or 1) + 1)
      end

      local icon = row:CreateTexture(nil, "ARTWORK")
      icon:SetSize(34, 34)
      icon:SetPoint("LEFT", 10, 0)
      row.icon = icon

      local item = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      item:SetPoint("TOPLEFT", 54, -9)
      item:SetWidth(480)
      item:SetJustifyH("LEFT")
      if item.SetWordWrap then
        item:SetWordWrap(false)
      end
      row.item = item

      local status = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      status:SetPoint("TOPLEFT", 54, -32)
      status:SetWidth(480)
      status:SetJustifyH("LEFT")
      row.status = status

      local tooltipButton = CreateFrame("Button", nil, row)
      tooltipButton:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
      tooltipButton:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -245, 0)
      tooltipButton:EnableMouse(true)
      tooltipButton:SetScript("OnEnter", ShowItemTooltip)
      tooltipButton:SetScript("OnLeave", HideItemTooltip)
      row.tooltipButton = tooltipButton

      if type(CreateCustomDropdownControl) == "function" then
        local dropdown = CreateCustomDropdownControl(nil, row, {
          width = 220,
          buttonHeight = 24,
          rowHeight = 20,
          maxVisibleEntries = 12,
          text = "No recommendation",
        })
        dropdown:SetPoint("RIGHT", row, "RIGHT", -12, 0)
        row.dropdown = dropdown
        RaisePopupControl(dropdown, frame, 30)
      end

      frame.recommendationRows[#frame.recommendationRows + 1] = row
    end
    return frame.recommendationRows
  end

  local function RefreshRecommendationsPopup(editor, profileName, targetFrame)
    local frame = targetFrame or EnsureRecommendationPopup(editor)
    if not frame then
      return false
    end
    if not RECOMMENDATION_HYDRATION.IsActive(editor, frame) then
      RECOMMENDATION_HYDRATION.Disarm(frame)
      return false
    end

    local resolvedProfileName = trim(profileName)
    local characterKeys, loggedCharacterKey = GetRecommendationCharacterKeys()
    local selectedCharacterKey = trim(frame.__recommendationTargetCharacterKey)
    local selectedCharacterIsLocal = false
    for _, characterKey in ipairs(characterKeys) do
      if characterKey == selectedCharacterKey then
        selectedCharacterIsLocal = true
        break
      end
    end
    if selectedCharacterKey == "" or not selectedCharacterIsLocal then
      selectedCharacterKey = loggedCharacterKey
    end
    if frame.__recommendationProfileName ~= resolvedProfileName or frame.__recommendationFiltersInitialized ~= true then
      frame.__recommendationProfileName = resolvedProfileName
      frame.__recommendationRaidInstanceID = nil
      frame.__recommendationEncounterID = nil
      frame.__recommendationDifficultyID = 14
      frame.__recommendationPerDifficulty = false
      local classID, specID = GetCurrentSpecFilter()
      frame.__recommendationFilterClassID = classID
      frame.__recommendationFilterSpecID = specID
      frame.__recommendationFiltersInitialized = true
    elseif frame.__recommendationFilterClassID == nil and frame.__recommendationFilterSpecID == nil then
      local classID, specID = GetCurrentSpecFilter()
      frame.__recommendationFilterClassID = classID
      frame.__recommendationFilterSpecID = specID
    end
    frame.__recommendationTargetCharacterKey = selectedCharacterKey
    frame.__recommendationLoggedCharacterKey = loggedCharacterKey
    frame.__recommendationRefresh = function()
      RefreshRecommendationsPopup(editor, profileName, frame)
    end

    local profile = type(GetLootProfile) == "function" and GetLootProfile(profileName) or nil
    local profileRaid = type(FindRaidEntryByProfile) == "function" and FindRaidEntryByProfile(profile) or nil
    local raids = CollectCurrentSeasonRaids(profile)
    local selectedRaid = FindRaidByInstanceID(raids, frame.__recommendationRaidInstanceID)
    if not selectedRaid and profileRaid then
      selectedRaid = FindRaidByInstanceID(raids, profileRaid.instanceID)
    end
    if not selectedRaid then
      selectedRaid = raids[1]
    end

    frame.__recommendationRaidInstanceID = selectedRaid and tonumber(selectedRaid.instanceID) or nil
    frame.__recommendationDifficultyID = tonumber(frame.__recommendationDifficultyID) or 14
    frame.__recommendationFilterClassID = tonumber(frame.__recommendationFilterClassID) or 0
    frame.__recommendationFilterSpecID = tonumber(frame.__recommendationFilterSpecID) or 0
    frame.__recommendationPerDifficulty = frame.__recommendationPerDifficulty == true

    local encounters = selectedRaid and type(CollectRaidEncounterEntries) == "function"
      and CollectRaidEncounterEntries(selectedRaid.instanceID)
      or {}
    local selectedEncounter = FindEncounterByID(encounters, frame.__recommendationEncounterID)
    frame.__recommendationEncounterID = selectedEncounter and tonumber(selectedEncounter.encounterID) or nil

    RECOMMENDATION_HYDRATION.BeginCycle(frame, table.concat({
      resolvedProfileName,
      selectedCharacterKey,
      tostring(frame.__recommendationRaidInstanceID or ""),
      tostring(frame.__recommendationEncounterID or "ALL"),
      tostring(frame.__recommendationDifficultyID or ""),
      tostring(frame.__recommendationFilterClassID or "ALL"),
      tostring(frame.__recommendationFilterSpecID or "ALL"),
      frame.__recommendationPerDifficulty == true and "PER_DIFFICULTY" or "SHARED",
    }, ":"))

    local items, journalDataPending = {}, false
    if selectedRaid and type(CollectRaidLootEntries) == "function" then
      items, journalDataPending = CollectRaidLootEntries(
        selectedRaid.instanceID,
        frame.__recommendationEncounterID,
        frame.__recommendationDifficultyID,
        frame.__recommendationFilterClassID,
        frame.__recommendationFilterSpecID,
        false
      )
    end
    local requestItemData = type(C_Item) == "table" and type(C_Item.RequestLoadItemDataByID) == "function"
      and C_Item.RequestLoadItemDataByID or nil
    RECOMMENDATION_HYDRATION.UpdatePendingItems(frame, items, journalDataPending, requestItemData)
    local options = GetRollOptions(profile)
    EnsureRows(frame, math.max(1, #items))
    local scrollWidth = frame.scroll and frame.scroll.GetWidth and tonumber(frame.scroll:GetWidth()) or 790
    local contentWidth = math.max(770, math.floor(scrollWidth - 20))

    if frame.popupTitle then
      frame.popupTitle:SetText(frame.__recommendationEmbedded and "Personal Roll Presets" or "Personal Recommendations Cockpit")
    end
    if frame.intro and frame.__recommendationEmbedded then
      local targetText = selectedCharacterKey ~= "" and selectedCharacterKey or "this character"
      local loggedText = loggedCharacterKey ~= "" and loggedCharacterKey or "unknown"
      local contextText = "Editing: " .. targetText
      if targetText ~= loggedText then
        contextText = contextText .. " | Logged in: " .. loggedText
      end
      frame.intro:SetText(contextText .. " | Profile: " .. resolvedProfileName .. " | Local only")
    end
    if frame.emptyText then
      if #raids == 0 then
        frame.emptyText:SetText("No profile-season raids were found in the Adventure Guide.")
      elseif selectedRaid and #items == 0 then
        frame.emptyText:SetText("No loot entries were found for the selected raid, boss, and difficulty.")
      else
        frame.emptyText:SetText("No raid loot entries are available for the selected filters.")
      end
      frame.emptyText:SetShown(#items == 0)
    end

    if frame.characterDropdown then
      SetDropdownValue(frame.characterDropdown, selectedCharacterKey, selectedCharacterKey ~= "" and selectedCharacterKey or "Current character")
      frame.characterDropdown:SetMenuBuilder(function()
        local entries = {}
        for _, characterKey in ipairs(characterKeys) do
          entries[#entries + 1] = {
            text = characterKey == loggedCharacterKey and (characterKey .. " (logged in)") or characterKey,
            searchText = characterKey,
            value = characterKey,
            checked = characterKey == selectedCharacterKey,
            func = function()
              frame.__recommendationTargetCharacterKey = characterKey
              frame.__recommendationRefresh()
            end,
          }
        end
        if #entries == 0 then
          entries[#entries + 1] = {
            text = "No local characters found",
            disabled = true,
            notCheckable = true,
          }
        end
        return {
          entries = entries,
          width = 330,
          maxVisibleEntries = 12,
          enableSearch = #entries > 12,
          searchPlaceholder = "Search characters",
        }
      end)
    end

    if frame.raidDropdown then
      SetDropdownValue(frame.raidDropdown, frame.__recommendationRaidInstanceID, selectedRaid and tostring(selectedRaid.name or selectedRaid.instanceID or "?") or "Select Raid")
      if frame.raidDropdown.SetEnabled then
        frame.raidDropdown:SetEnabled(#raids > 0)
      end
      frame.raidDropdown:SetMenuBuilder(function()
        local entries = {}
        for _, raid in ipairs(raids) do
          local instanceID = tonumber(raid.instanceID)
          entries[#entries + 1] = {
            text = tostring(raid.name or instanceID or "?"),
            value = instanceID,
            checked = tonumber(frame.__recommendationRaidInstanceID) == instanceID,
            func = function()
              frame.__recommendationRaidInstanceID = instanceID
              frame.__recommendationEncounterID = nil
              frame.__recommendationRefresh()
            end,
          }
        end
        if #entries == 0 then
          entries[#entries + 1] = {
            text = "No profile-season raids",
            disabled = true,
            notCheckable = true,
          }
        end
        return {
          entries = entries,
          width = 290,
          maxVisibleEntries = 12,
          enableSearch = #entries > 12,
          searchPlaceholder = "Search raids",
        }
      end)
    end

    if frame.bossDropdown then
      SetDropdownValue(frame.bossDropdown, frame.__recommendationEncounterID, selectedEncounter and tostring(selectedEncounter.name or selectedEncounter.encounterID or "?") or "All Bosses")
      if frame.bossDropdown.SetEnabled then
        frame.bossDropdown:SetEnabled(selectedRaid ~= nil)
      end
      frame.bossDropdown:SetMenuBuilder(function()
        local entries = {
          {
            text = "All Bosses",
            checked = frame.__recommendationEncounterID == nil,
            func = function()
              frame.__recommendationEncounterID = nil
              frame.__recommendationRefresh()
            end,
          },
        }
        for _, encounter in ipairs(encounters) do
          local encounterID = tonumber(encounter.encounterID)
          entries[#entries + 1] = {
            text = tostring(encounter.name or encounterID or "?"),
            searchText = tostring(encounter.name or encounterID or "?"),
            value = encounterID,
            checked = tonumber(frame.__recommendationEncounterID) == encounterID,
            func = function()
              frame.__recommendationEncounterID = encounterID
              frame.__recommendationRefresh()
            end,
          }
        end
        if #entries == 1 and not selectedRaid then
          entries[1] = {
            text = "Select a raid first",
            disabled = true,
            notCheckable = true,
          }
        end
        return {
          entries = entries,
          width = 290,
          maxVisibleEntries = 12,
          enableSearch = #entries > 12,
          searchPlaceholder = "Search bosses",
        }
      end)
    end

    if frame.difficultyDropdown then
      SetDropdownValue(frame.difficultyDropdown, frame.__recommendationDifficultyID, GetDifficultyName(frame.__recommendationDifficultyID))
      frame.difficultyDropdown:SetMenuBuilder(function()
        local entries = {}
        for _, difficulty in ipairs(GetDifficultyEntries()) do
          local difficultyID = tonumber(difficulty.id)
          entries[#entries + 1] = {
            text = tostring(difficulty.name or difficultyID or "?"),
            value = difficultyID,
            checked = tonumber(frame.__recommendationDifficultyID) == difficultyID,
            func = function()
              frame.__recommendationDifficultyID = difficultyID
              frame.__recommendationRefresh()
            end,
          }
        end
        return {
          entries = entries,
          width = 150,
          maxVisibleEntries = 6,
        }
      end)
    end

    if frame.perDifficultyCheck then
      frame.perDifficultyCheck:SetChecked(frame.__recommendationPerDifficulty)
    end

    if frame.specFilterDropdown then
      local specFilterEntry = FindSpecFilterEntry(frame.__recommendationFilterClassID, frame.__recommendationFilterSpecID)
      SetDropdownValue(frame.specFilterDropdown, frame.__recommendationFilterSpecID, specFilterEntry and tostring(specFilterEntry.text or "All Classes / Specs") or "All Classes / Specs")
      frame.specFilterDropdown:SetMenuBuilder(function()
        local entries = {}
        for _, filterEntry in ipairs(BuildSpecFilterEntries()) do
          local classID = tonumber(filterEntry.classID) or 0
          local specID = tonumber(filterEntry.specID) or 0
          entries[#entries + 1] = {
            text = tostring(filterEntry.text or "Spec"),
            searchText = tostring(filterEntry.text or "Spec"),
            icon = filterEntry.icon,
            iconSize = filterEntry.iconSize,
            checked = tonumber(frame.__recommendationFilterClassID) == classID and tonumber(frame.__recommendationFilterSpecID) == specID,
            func = function()
              frame.__recommendationFilterClassID = classID
              frame.__recommendationFilterSpecID = specID
              frame.__recommendationRefresh()
            end,
          }
        end
        return {
          entries = entries,
          width = 360,
          maxVisibleEntries = 14,
          enableSearch = #entries > 14,
          searchPlaceholder = "Search class or spec",
        }
      end)
    end

    for index, row in ipairs(frame.recommendationRows or {}) do
      local item = items[index]
      if type(item) == "table" then
        row:ClearAllPoints()
        row:SetPoint("TOPLEFT", frame.content, "TOPLEFT", 0, -((index - 1) * 62))
        row:SetSize(contentWidth, 56)
        if row.item then
          row.item:SetWidth(math.max(220, contentWidth - 300))
        end
        if row.status then
          row.status:SetWidth(math.max(220, contentWidth - 300))
        end
        if row.SetFrameLevel and frame.content and frame.content.GetFrameLevel then
          row:SetFrameLevel((frame.content:GetFrameLevel() or 1) + 1)
        end
        if row.dropdown then
          RaisePopupControl(row.dropdown, frame, 30)
        end
        if row.tooltipButton then
          row.tooltipButton.__recommendationItem = item
          if row.tooltipButton.SetFrameLevel and row.GetFrameLevel then
            row.tooltipButton:SetFrameLevel((row:GetFrameLevel() or 1) + 1)
          end
        end

        if row.icon then
          row.icon:SetTexture(item.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
        end
        if row.item then
          local label = type(FormatRaidLootItemLabel) == "function" and FormatRaidLootItemLabel(item) or trim(item.name)
          row.item:SetText(label ~= "" and label or "Unknown Item")
        end

        local recommendationOptions = BuildRecommendationOptions(frame, item)
        local recommendationScope = FormatRecommendationScope(frame, item)
        local recommendation, option, status = ResolveRecommendation(profileName, item, options, recommendationOptions)
        local priority = tonumber(type(recommendation) == "table" and recommendation.priority) or nil
        if row.status then
          if status == "OK" and type(option) == "table" then
            row.status:SetText("Recommended" .. recommendationScope .. ": " .. FormatOptionText(priority or 0, option))
            row.status:SetTextColor(1.00, 0.86, 0.26, 1)
          elseif status == "MISSING" then
            local storedLabel = trim(type(recommendation) == "table" and recommendation.optionLabel or "")
            row.status:SetText("Missing" .. recommendationScope .. ": priority #" .. tostring(priority or "?") .. (storedLabel ~= "" and (" " .. storedLabel) or "") .. " no longer exists")
            row.status:SetTextColor(1.00, 0.38, 0.28, 1)
          else
            row.status:SetText("No recommendation" .. recommendationScope)
            row.status:SetTextColor(0.72, 0.72, 0.76, 1)
          end
        end

        if row.dropdown then
          if status == "OK" and type(option) == "table" then
            row.dropdown:SetText(FormatOptionText(priority or 0, option))
          elseif status == "MISSING" then
            local storedLabel = trim(type(recommendation) == "table" and recommendation.optionLabel or "")
            row.dropdown:SetText("Missing #" .. tostring(priority or "?") .. (storedLabel ~= "" and (": " .. storedLabel) or ""))
          else
            row.dropdown:SetText("No recommendation")
          end
          ApplyRecommendationDropdownTheme(row.dropdown, status == "OK" and option or nil, status)
          row.dropdown:SetMenuBuilder(function()
            local entries = {
              {
                text = "No recommendation",
                checked = status == "NONE",
                notCheckable = false,
                func = function()
                  ClearRecommendation(profileName, item, recommendationOptions)
                  frame.__recommendationRefresh()
                end,
              },
            }
            for optionIndex, rollOption in ipairs(options) do
              entries[#entries + 1] = {
                text = FormatOptionText(optionIndex, rollOption),
                searchText = FormatOptionText(optionIndex, rollOption),
                icon = type(ResolveRollOptionIcon) == "function" and ResolveRollOptionIcon(rollOption) or nil,
                checked = status == "OK" and tonumber(priority) == optionIndex,
                func = function()
                  SetRecommendation(profileName, item, optionIndex, rollOption, recommendationOptions)
                  frame.__recommendationRefresh()
                end,
              }
            end
            if #options == 0 then
              entries[#entries + 1] = {
                text = "No roll options configured",
                disabled = true,
                notCheckable = true,
              }
            end
            return {
              entries = entries,
              width = 245,
              maxVisibleEntries = 12,
              enableSearch = #entries > 12,
              searchPlaceholder = "Search options",
            }
          end)
        end
        row:Show()
      else
        row:Hide()
      end
    end

    local height = math.max(1, (#items * 62) + 8)
    frame.content:SetSize(contentWidth, height)
    return true
  end

  local function OpenLootListRecommendationsPopup(editor, profileName)
    local frame = EnsureRecommendationPopup(editor)
    if not frame then
      return false
    end
    frame:Show()
    RefreshRecommendationsPopup(editor, profileName)
    if frame.Raise then
      frame:Raise()
    end
    return true
  end

  local function MountLootListRecommendations(editor, parent, profileName)
    if not parent then
      return false
    end
    local frame = EnsureEmbeddedRecommendationView(editor, parent)
    if not frame then
      return false
    end
    frame:Show()
    return RefreshRecommendationsPopup(editor, profileName, frame)
  end

  return {
    OpenLootListRecommendationsPopup = OpenLootListRecommendationsPopup,
    MountLootListRecommendations = MountLootListRecommendations,
    RefreshRecommendationsPopup = RefreshRecommendationsPopup,
    IsLootListRecommendationsPopupShown = function(editor)
      return editor and editor.lootLedgerRecommendationsPopupFrame and editor.lootLedgerRecommendationsPopupFrame:IsShown() or false
    end,
  }
end
