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

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

  local ApplyFrameSkin = deps.ApplyFrameSkin
  local TrimText = deps.TrimText
  local GetSubdomainLabel = deps.GetSubdomainLabel
  local GetSubdomainParentDomain = deps.GetSubdomainParentDomain

  local DOMAIN_ORDER = {
    settings = 1,
    permissions = 2,
    loot = 3,
    session = 4,
  }
  local DOMAIN_LABELS = {
    settings = "Settings",
    permissions = "Permissions",
    loot = "Loot",
    session = "Session",
  }
  local ACTION_COLORS = {
    push = { 0.28, 0.72, 1.00, 1.00 },
    pull = { 1.00, 0.82, 0.32, 1.00 },
    preview = { 0.94, 0.34, 0.28, 1.00 },
    neutral = { 0.82, 0.84, 0.92, 1.00 },
  }
  local TRANSPORT_COLORS = {
    patch_ready = { 0.30, 0.90, 0.36, 1.00 },
    patch_later = { 1.00, 0.82, 0.32, 1.00 },
    snapshot = { 0.82, 0.84, 0.92, 1.00 },
  }

  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 CloneList(values)
    local clone = {}
    for _, value in ipairs(type(values) == "table" and values or {}) do
      clone[#clone + 1] = tostring(value)
    end
    return clone
  end

  local function BuildLookup(values)
    local lookup = {}
    for _, value in ipairs(type(values) == "table" and values or {}) do
      local key = string.lower(trim(value))
      if key ~= "" then
        lookup[key] = tostring(value)
      end
    end
    return lookup
  end

  local function GetScopeLabel(subdomain)
    local resolved = trim(subdomain)
    if resolved == "" then
      return "-"
    end
    if type(GetSubdomainLabel) == "function" then
      local label = trim(GetSubdomainLabel(resolved))
      if label ~= "" then
        return label
      end
    end
    return resolved
  end

  local function GetScopeDomain(subdomain)
    local resolved = trim(subdomain)
    if resolved ~= "" and type(GetSubdomainParentDomain) == "function" then
      return string.lower(trim(GetSubdomainParentDomain(resolved)))
    end
    return ""
  end

  local function CompareScopeRows(left, right)
    local leftDomain = DOMAIN_ORDER[left.domain] or 99
    local rightDomain = DOMAIN_ORDER[right.domain] or 99
    if leftDomain ~= rightDomain then
      return leftDomain < rightDomain
    end
    if left.actionPriority ~= right.actionPriority then
      return left.actionPriority < right.actionPriority
    end
    local leftLabel = string.lower(tostring(left.label or ""))
    local rightLabel = string.lower(tostring(right.label or ""))
    if leftLabel ~= rightLabel then
      return leftLabel < rightLabel
    end
    return tostring(left.subdomain or "") < tostring(right.subdomain or "")
  end

  local function BuildScopeRows(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    if candidate.preciseDetailLoaded ~= true then
      return {}
    end

    local pushLookup = BuildLookup(candidate.precisePushSubdomains)
    local pullLookup = BuildLookup(candidate.precisePullSubdomains)
    local previewLookup = BuildLookup(candidate.precisePreviewSubdomains)
    local patchPushLookup = BuildLookup(candidate.patchPushSubdomains)
    local patchPullLookup = BuildLookup(candidate.patchPullSubdomains)
    local patchPreviewLookup = BuildLookup(candidate.patchPreviewSubdomains)
    local combinedLookup = {}

    for key, subdomain in pairs(previewLookup) do
      combinedLookup[key] = subdomain
    end
    for key, subdomain in pairs(pushLookup) do
      combinedLookup[key] = subdomain
    end
    for key, subdomain in pairs(pullLookup) do
      combinedLookup[key] = subdomain
    end

    local rows = {}
    for _, subdomain in pairs(combinedLookup) do
      local normalizedKey = string.lower(trim(subdomain))
      local domain = GetScopeDomain(subdomain)
      local inPush = pushLookup[normalizedKey] ~= nil
      local inPull = pullLookup[normalizedKey] ~= nil
      local inPreview = previewLookup[normalizedKey] ~= nil
      local actionKind = "preview"
      local actionText = "Preview First"
      local actionPriority = 3
      if inPush and not inPull then
        actionKind = "push"
        actionText = "Push Host"
        actionPriority = 1
      elseif inPull and not inPush then
        actionKind = "pull"
        actionText = "Pull Member"
        actionPriority = 2
      end

      local patchReady = false
      local patchLater = false
      if actionKind == "push" then
        patchReady = candidate.patchPushReady == true and patchPushLookup[normalizedKey] ~= nil
        patchLater = patchReady ~= true and patchPushLookup[normalizedKey] ~= nil
      elseif actionKind == "pull" then
        patchReady = patchPullLookup[normalizedKey] ~= nil
      else
        patchReady = patchPreviewLookup[normalizedKey] ~= nil
      end
      local transportKey = "snapshot"
      local transportText = "Snapshot"
      if patchReady then
        transportKey = "patch_ready"
        transportText = "Patch-ready"
      elseif patchLater then
        transportKey = "patch_later"
        transportText = "Patch later"
      end

      rows[#rows + 1] = {
        subdomain = subdomain,
        label = GetScopeLabel(subdomain),
        domain = domain,
        domainLabel = DOMAIN_LABELS[domain] or (domain ~= "" and domain or "-"),
        actionKind = actionKind,
        actionText = actionText,
        actionPriority = actionPriority,
        transportKey = transportKey,
        transportText = transportText,
        pushAllowed = inPush,
        pullAllowed = inPull,
        previewAllowed = inPreview,
      }
    end
    table.sort(rows, CompareScopeRows)
    return rows
  end

  local function GetSelectionStore(editor)
    editor.lootSessionSyncScopeSelections = type(editor.lootSessionSyncScopeSelections) == "table"
      and editor.lootSessionSyncScopeSelections
      or {}
    return editor.lootSessionSyncScopeSelections
  end

  local function NormalizeMemberSelection(editor, candidate, rows)
    local memberKey = trim(type(candidate) == "table" and candidate.memberKey or "")
    if memberKey == "" then
      return {
        selected = {},
      }
    end
    local store = GetSelectionStore(editor)
    local entry = type(store[memberKey]) == "table" and store[memberKey] or {
      selected = {},
    }
    entry.selected = type(entry.selected) == "table" and entry.selected or {}

    local valid = {}
    for _, row in ipairs(rows or {}) do
      valid[string.lower(trim(row.subdomain))] = tostring(row.subdomain)
    end
    for key in pairs(entry.selected) do
      if valid[string.lower(trim(key))] == nil then
        entry.selected[key] = nil
      end
    end

    store[memberKey] = entry
    return entry
  end

  local function SetSelectedSubdomains(editor, candidate, subdomains)
    local rows = BuildScopeRows(candidate)
    local entry = NormalizeMemberSelection(editor, candidate, rows)
    entry.selected = {}
    for _, subdomain in ipairs(type(subdomains) == "table" and subdomains or {}) do
      local resolved = trim(subdomain)
      if resolved ~= "" then
        entry.selected[resolved] = true
      end
    end
  end

  local function BuildDomainsForSubdomains(rows, subdomains)
    local domains = {}
    local seen = {}
    local rowBySubdomain = {}
    for _, row in ipairs(rows or {}) do
      rowBySubdomain[string.lower(trim(row.subdomain))] = row
    end
    for _, subdomain in ipairs(type(subdomains) == "table" and subdomains or {}) do
      local row = rowBySubdomain[string.lower(trim(subdomain))]
      local domain = type(row) == "table" and trim(row.domain) or ""
      if domain ~= "" and not seen[domain] then
        seen[domain] = true
        domains[#domains + 1] = domain
      end
    end
    table.sort(domains, function(left, right)
      return (DOMAIN_ORDER[left] or 99) < (DOMAIN_ORDER[right] or 99)
    end)
    return domains
  end

  local function BuildSelectedScopeAction(candidate, rows, selectionEntry, actionKind)
    local selectedCount = 0
    local compatible = {}
    for _, row in ipairs(rows or {}) do
      local selected = type(selectionEntry) == "table"
        and type(selectionEntry.selected) == "table"
        and selectionEntry.selected[trim(row.subdomain)] == true
      if selected then
        selectedCount = selectedCount + 1
        if (actionKind == "push" and row.pushAllowed)
          or (actionKind == "pull" and row.pullAllowed)
          or (actionKind == "preview" and row.previewAllowed) then
          compatible[#compatible + 1] = tostring(row.subdomain)
        end
      end
    end

    return {
      selectionActive = selectedCount > 0,
      selectedCount = selectedCount,
      compatibleCount = #compatible,
      incompatibleCount = math.max(0, selectedCount - #compatible),
      subdomains = CloneList(compatible),
      domains = BuildDomainsForSubdomains(rows, compatible),
      targetMemberKey = trim(type(candidate) == "table" and candidate.memberKey or ""),
    }
  end

  local function BuildSelectionHint(rows, actions)
    local totalCount = #(rows or {})
    local preview = type(actions) == "table" and type(actions.preview) == "table" and actions.preview or {}
    local push = type(actions) == "table" and type(actions.push) == "table" and actions.push or {}
    local pull = type(actions) == "table" and type(actions.pull) == "table" and actions.pull or {}
    if totalCount == 0 then
      return ""
    end
    if preview.selectionActive then
      return string.format(
        "Leaf filter active: %d selected | Push %d | Pull %d | Preview %d. Click rows below to change the repair scope.",
        tonumber(preview.selectedCount) or 0,
        tonumber(push.compatibleCount) or 0,
        tonumber(pull.compatibleCount) or 0,
        tonumber(preview.compatibleCount) or 0
      )
    end
    return "Click rows below to narrow Push / Pull / Preview to exact leaf scopes. Leaving all rows clear keeps the broader member/domain repair behavior."
  end

  local function EnsureScopePicker(editor)
    if not editor then
      return nil
    end
    if editor.lootSessionSyncScopeFrame then
      return editor.lootSessionSyncScopeFrame
    end

    local parent = editor.lootSessionRightContent or editor.lootSessionRightBg or UIParent
    local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    ApplyFrameSkin(frame)
    if frame.SetBackdropColor then
      frame:SetBackdropColor(0.05, 0.06, 0.08, 0.96)
    end

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetJustifyH("LEFT")
    title:SetText("Leaf Scope Picker")
    frame.title = title

    local recommendedBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    recommendedBtn:SetSize(108, 20)
    recommendedBtn:SetText("Recommended")
    frame.recommendedBtn = recommendedBtn

    local allBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    allBtn:SetSize(56, 20)
    allBtn:SetText("All")
    frame.allBtn = allBtn

    local clearBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    clearBtn:SetSize(56, 20)
    clearBtn:SetText("Clear")
    frame.clearBtn = clearBtn

    local summaryText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    summaryText:SetJustifyH("LEFT")
    summaryText:SetJustifyV("TOP")
    frame.summaryText = summaryText

    local columns = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    ApplyFrameSkin(columns)
    if columns.SetBackdropColor then
      columns:SetBackdropColor(0.07, 0.08, 0.11, 0.98)
    end
    frame.columns = columns
    frame.headers = {}
    local headerDefs = {
      { key = "scope", text = "Scope" },
      { key = "domain", text = "Domain" },
      { key = "action", text = "Suggested" },
      { key = "transport", text = "Transport" },
    }
    for _, headerDef in ipairs(headerDefs) do
      local header = columns:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      header:SetJustifyH("LEFT")
      header:SetText(headerDef.text)
      frame.headers[headerDef.key] = header
    end

    local scrollFrame = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
    if scrollFrame.ScrollBar then
      scrollFrame.ScrollBar:ClearAllPoints()
      scrollFrame.ScrollBar:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -6, -86)
      scrollFrame.ScrollBar:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -6, 12)
    end
    frame.scrollFrame = scrollFrame

    local scrollChild = CreateFrame("Frame", nil, scrollFrame)
    scrollChild:SetSize(1, 1)
    scrollFrame:SetScrollChild(scrollChild)
    frame.scrollChild = scrollChild

    frame.rows = {}
    editor.lootSessionSyncScopeFrame = frame
    return frame
  end

  local function EnsureScopeRows(frame, count, editor)
    frame.rows = frame.rows or {}
    while #frame.rows < count do
      local row = CreateFrame("Frame", nil, frame.scrollChild, "BackdropTemplate")
      ApplyFrameSkin(row)
      if row.SetBackdropColor then
        row:SetBackdropColor(0.06, 0.07, 0.10, 0.96)
      end
      row:SetHeight(24)
      row:EnableMouse(true)

      local check = CreateFrame("CheckButton", nil, row, "UICheckButtonTemplate")
      check:SetSize(18, 18)
      row.check = check

      local scopeText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      scopeText:SetJustifyH("LEFT")
      row.scopeText = scopeText

      local domainText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      domainText:SetJustifyH("LEFT")
      row.domainText = domainText

      local actionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      actionText:SetJustifyH("LEFT")
      row.actionText = actionText

      local transportText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      transportText:SetJustifyH("LEFT")
      row.transportText = transportText

      local function ToggleRowSelection(checked)
        local candidate = frame.__candidate
        local rows = type(frame.__rows) == "table" and frame.__rows or {}
        local entry = NormalizeMemberSelection(editor, candidate, rows)
        local subdomain = trim(row.__subdomain)
        if subdomain ~= "" then
          if checked then
            entry.selected[subdomain] = true
          else
            entry.selected[subdomain] = nil
          end
        end
        if editor and editor.Refresh then
          editor:Refresh()
        end
      end

      check:SetScript("OnClick", function(self)
        ToggleRowSelection(self:GetChecked() and true or false)
      end)
      row:SetScript("OnMouseUp", function()
        check:SetChecked(not (check:GetChecked() and true or false))
        ToggleRowSelection(check:GetChecked() and true or false)
      end)

      frame.rows[#frame.rows + 1] = row
    end
  end

  local function RefreshSyncScopePicker(editor, model)
    local frame = EnsureScopePicker(editor)
    if not frame then
      return {
        actions = {},
        selectionHint = "",
      }
    end

    model = type(model) == "table" and model or {}
    local candidate = type(model.candidate) == "table" and model.candidate or nil
    local rows = BuildScopeRows(candidate)
    local selectionEntry = NormalizeMemberSelection(editor, candidate, rows)
    local actions = {
      push = BuildSelectedScopeAction(candidate, rows, selectionEntry, "push"),
      pull = BuildSelectedScopeAction(candidate, rows, selectionEntry, "pull"),
      preview = BuildSelectedScopeAction(candidate, rows, selectionEntry, "preview"),
    }
    local selectionHint = BuildSelectionHint(rows, actions)

    frame.__candidate = candidate
    frame.__rows = rows
    frame.__actions = actions
    frame.__selectionHint = selectionHint

    if not candidate then
      frame.summaryText:SetText("Select one joined member above to choose clickable individual leaf repair scopes.")
    elseif candidate.preciseDetailLoaded ~= true then
      frame.summaryText:SetText("Detailed leaf scopes are not loaded for this member yet. Use Check Differences first if you want clickable per-leaf repair selection.")
    elseif #rows == 0 then
      frame.summaryText:SetText("No differing leaf scopes are loaded for this member.")
    else
      frame.summaryText:SetText(
        string.format(
          "Loaded %d differing leaf scope%s. %s",
          #rows,
          #rows == 1 and "" or "s",
          selectionHint
        )
      )
    end

    frame.recommendedBtn:SetShown(#rows > 0)
    frame.allBtn:SetShown(#rows > 0)
    frame.clearBtn:SetShown(#rows > 0)

    frame.recommendedBtn:SetScript("OnClick", function()
      local defaultScopes = {}
      if type(candidate) == "table" then
        local comparison = string.lower(trim(candidate.comparison))
        if comparison == "local_newer" then
          defaultScopes = candidate.precisePushSubdomains or {}
        elseif comparison == "remote_newer" then
          defaultScopes = candidate.precisePullSubdomains or {}
        else
          defaultScopes = candidate.precisePreviewSubdomains or {}
        end
      end
      SetSelectedSubdomains(editor, candidate, defaultScopes)
      if editor and editor.Refresh then
        editor:Refresh()
      end
    end)
    frame.allBtn:SetScript("OnClick", function()
      local allScopes = {}
      for _, row in ipairs(rows) do
        allScopes[#allScopes + 1] = row.subdomain
      end
      SetSelectedSubdomains(editor, candidate, allScopes)
      if editor and editor.Refresh then
        editor:Refresh()
      end
    end)
    frame.clearBtn:SetScript("OnClick", function()
      SetSelectedSubdomains(editor, candidate, {})
      if editor and editor.Refresh then
        editor:Refresh()
      end
    end)

    EnsureScopeRows(frame, #rows, editor)
    for index, row in ipairs(frame.rows or {}) do
      local rowData = rows[index]
      if rowData then
        row.__subdomain = rowData.subdomain
        local selected = type(selectionEntry.selected) == "table" and selectionEntry.selected[trim(rowData.subdomain)] == true
        row.check:SetChecked(selected and true or false)

        row.scopeText:SetText(rowData.label)
        row.scopeText:SetTextColor(0.88, 0.90, 0.96, 1.00)

        row.domainText:SetText(rowData.domainLabel)
        row.domainText:SetTextColor(0.76, 0.80, 0.90, 1.00)

        local actionColor = ACTION_COLORS[rowData.actionKind] or ACTION_COLORS.neutral
        row.actionText:SetText(rowData.actionText)
        row.actionText:SetTextColor(actionColor[1], actionColor[2], actionColor[3], actionColor[4])

        local transportColor = TRANSPORT_COLORS[rowData.transportKey] or TRANSPORT_COLORS.snapshot
        row.transportText:SetText(rowData.transportText)
        row.transportText:SetTextColor(transportColor[1], transportColor[2], transportColor[3], transportColor[4])

        if row.SetBackdropBorderColor then
          if selected then
            row:SetBackdropBorderColor(1.00, 0.82, 0.32, 1.00)
          else
            row:SetBackdropBorderColor(0.22, 0.24, 0.30, 0.80)
          end
        end
        row:Show()
      else
        row.__subdomain = nil
        row:Hide()
      end
    end

    frame.__rowCount = #rows
    return {
      actions = actions,
      selectionHint = selectionHint,
    }
  end

  local function LayoutSyncScopePicker(editor, topOffset, rightContentWidth)
    local frame = EnsureScopePicker(editor)
    if not frame then
      return 0
    end

    local width = math.max(320, tonumber(rightContentWidth) or 320)
    frame:ClearAllPoints()
    frame:SetPoint("TOPLEFT", 18, topOffset)
    frame:SetWidth(width)

    frame.title:ClearAllPoints()
    frame.title:SetPoint("TOPLEFT", 14, -12)

    frame.clearBtn:ClearAllPoints()
    frame.clearBtn:SetPoint("TOPRIGHT", -14, -10)
    frame.allBtn:ClearAllPoints()
    frame.allBtn:SetPoint("RIGHT", frame.clearBtn, "LEFT", -6, 0)
    frame.recommendedBtn:ClearAllPoints()
    frame.recommendedBtn:SetPoint("RIGHT", frame.allBtn, "LEFT", -6, 0)

    frame.summaryText:ClearAllPoints()
    frame.summaryText:SetPoint("TOPLEFT", 14, -38)
    frame.summaryText:SetWidth(width - 28)
    local summaryHeight = math.max(34, math.ceil(frame.summaryText:GetStringHeight() or 0))

    local rowsVisible = math.min(math.max(tonumber(frame.__rowCount) or 0, 0), 7)
    local showTable = (tonumber(frame.__rowCount) or 0) > 0
    local headerTop = -38 - summaryHeight - 10
    local rowAreaHeight = rowsVisible > 0 and (rowsVisible * 24) or 0

    frame.columns:SetShown(showTable)
    frame.scrollFrame:SetShown(showTable)
    if showTable then
      frame.columns:ClearAllPoints()
      frame.columns:SetPoint("TOPLEFT", 14, headerTop)
      frame.columns:SetSize(width - 42, 22)

      local innerWidth = width - 42
      local scopeWidth = math.max(156, math.floor(innerWidth * 0.42))
      local domainWidth = math.max(74, math.floor(innerWidth * 0.18))
      local actionWidth = math.max(104, math.floor(innerWidth * 0.22))
      local transportWidth = math.max(74, innerWidth - scopeWidth - domainWidth - actionWidth - 18)
      local offsets = {
        scope = 28,
        domain = 28 + scopeWidth,
        action = 28 + scopeWidth + domainWidth,
        transport = 28 + scopeWidth + domainWidth + actionWidth,
      }
      local widths = {
        scope = scopeWidth,
        domain = domainWidth,
        action = actionWidth,
        transport = transportWidth,
      }
      for key, header in pairs(frame.headers or {}) do
        header:ClearAllPoints()
        header:SetPoint("TOPLEFT", offsets[key] or 8, -4)
        header:SetWidth(widths[key] or 80)
      end

      frame.scrollFrame:ClearAllPoints()
      frame.scrollFrame:SetPoint("TOPLEFT", 14, headerTop - 26)
      frame.scrollFrame:SetSize(width - 28, rowAreaHeight)

      frame.scrollChild:SetWidth(width - 48)
      frame.scrollChild:SetHeight(math.max(1, (tonumber(frame.__rowCount) or 0) * 24))
      for index, row in ipairs(frame.rows or {}) do
        row:ClearAllPoints()
        row:SetPoint("TOPLEFT", 0, -((index - 1) * 24))
        row:SetSize(width - 48, 22)

        row.check:ClearAllPoints()
        row.check:SetPoint("TOPLEFT", 2, -2)

        row.scopeText:ClearAllPoints()
        row.scopeText:SetPoint("TOPLEFT", offsets.scope, -4)
        row.scopeText:SetWidth(widths.scope)

        row.domainText:ClearAllPoints()
        row.domainText:SetPoint("TOPLEFT", offsets.domain, -4)
        row.domainText:SetWidth(widths.domain)

        row.actionText:ClearAllPoints()
        row.actionText:SetPoint("TOPLEFT", offsets.action, -4)
        row.actionText:SetWidth(widths.action)

        row.transportText:ClearAllPoints()
        row.transportText:SetPoint("TOPLEFT", offsets.transport, -4)
        row.transportText:SetWidth(widths.transport)
      end
    end

    local totalHeight = 52 + summaryHeight + (showTable and (24 + rowAreaHeight) or 0) + 14
    frame:SetHeight(totalHeight)
    return totalHeight
  end

  return {
    RefreshSyncScopePicker = RefreshSyncScopePicker,
    LayoutSyncScopePicker = LayoutSyncScopePicker,
  }
end
