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_CreateLootSessionSyncDashboardHelpers_Impl = function(deps)
  deps = type(deps) == "table" and deps or {}

  local ApplyFrameSkin = deps.ApplyFrameSkin
  local TrimText = deps.TrimText
  local FormatDateTime = deps.FormatDateTime
  local GetSessionClassColor = deps.GetSessionClassColor
  local OpenProfileSyncDetail = deps.OpenProfileSyncDetail

  local DOMAIN_ORDER = { "settings", "permissions", "loot", "session" }
  local STATUS_COLORS = {
    same = { 0.25, 0.86, 0.42, 1.00 },
    ready = { 0.25, 0.86, 0.42, 1.00 },
    pending = { 1.00, 0.82, 0.32, 1.00 },
    review = { 0.94, 0.34, 0.28, 1.00 },
    local_newer = { 0.28, 0.72, 1.00, 1.00 },
    remote_newer = { 1.00, 0.82, 0.32, 1.00 },
    diverged = { 0.94, 0.34, 0.28, 1.00 },
    different_lineage = { 0.94, 0.62, 0.28, 1.00 },
    lineage = { 0.94, 0.62, 0.28, 1.00 },
    unknown = { 0.78, 0.78, 0.84, 1.00 },
    member_newer = { 1.00, 0.82, 0.32, 1.00 },
    host_newer = { 0.28, 0.72, 1.00, 1.00 },
    conflict = { 0.94, 0.34, 0.28, 1.00 },
  }

  local function GetStatusColor(key)
    return unpack(STATUS_COLORS[key] or STATUS_COLORS.unknown)
  end

  local function HasDomain(domains, domain)
    local needle = string.lower(TrimText(domain))
    if needle == "" then
      return false
    end
    for _, value in ipairs(type(domains) == "table" and domains or {}) do
      if string.lower(TrimText(value)) == needle then
        return true
      end
    end
    return false
  end

  local function GetDomainState(candidate, domain)
    candidate = type(candidate) == "table" and candidate or {}
    local comparison = string.lower(TrimText(candidate.comparison))
    if comparison == "different_lineage" then
      return "lineage"
    end
    local remote = HasDomain(candidate.remoteNewerDomains, domain)
    local localNewer = HasDomain(candidate.localNewerDomains, domain)
    if remote and localNewer then
      return "conflict"
    elseif remote then
      return "member_newer"
    elseif localNewer then
      return "host_newer"
    end
    return "same"
  end

  local function GetDomainLabel(state)
    if state == "same" then
      return "OK"
    elseif state == "member_newer" then
      return "Member"
    elseif state == "host_newer" then
      return "Host"
    elseif state == "conflict" then
      return "Both"
    elseif state == "lineage" then
      return "Lineage"
    end
    return "-"
  end

  local function GetComparisonLabel(comparison, isHost)
    local resolved = string.lower(TrimText(comparison))
    if resolved == "same" then
      return "Aligned"
    elseif resolved == "local_newer" then
      return isHost and "Host Newer" or "You Newer"
    elseif resolved == "remote_newer" then
      return isHost and "Member Newer" or "Remote Newer"
    elseif resolved == "diverged" then
      return "Conflict"
    elseif resolved == "different_lineage" then
      return "Lineage"
    end
    return "Unknown"
  end

  local function GetBootstrapState(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    local state = string.lower(TrimText(candidate.bootstrapState))
    if state ~= "" then
      return state
    end
    if string.lower(TrimText(candidate.comparison)) == "different_lineage" then
      return "lineage"
    elseif string.lower(TrimText(candidate.comparison)) == "unknown" or TrimText(candidate.profileId) == "" then
      return "pending"
    end
    local settingsState = GetDomainState(candidate, "settings")
    local permissionsState = GetDomainState(candidate, "permissions")
    if settingsState == "same" and permissionsState == "same" then
      return "ready"
    end
    if settingsState == "member_newer" or settingsState == "conflict"
        or permissionsState == "member_newer" or permissionsState == "conflict" then
      return "review"
    end
    if settingsState == "host_newer" or permissionsState == "host_newer" then
      return "pending"
    end
    return "unknown"
  end

  local function GetBootstrapLabel(state)
    local resolved = string.lower(TrimText(state))
    if resolved == "ready" then
      return "Ready"
    elseif resolved == "pending" then
      return "Wait"
    elseif resolved == "review" then
      return "Review"
    elseif resolved == "lineage" then
      return "Lineage"
    end
    return "-"
  end

  local function GetActionLabel(candidate, isHost)
    candidate = type(candidate) == "table" and candidate or {}
    local recommendation = type(candidate.safeRecommendation) == "table" and candidate.safeRecommendation or nil
    if isHost and type(recommendation) == "table" and TrimText(recommendation.label) ~= "" then
      return TrimText(recommendation.label)
    end
    local comparison = string.lower(TrimText(candidate.comparison))
    if comparison == "same" then
      return "No action"
    elseif comparison == "local_newer" then
      if isHost then
        return candidate.precisePushReady and "Push precise" or "Push host repair"
      end
      return "Wait for host push"
    elseif comparison == "remote_newer" then
      if isHost then
        return candidate.precisePullReady and "Pull precise" or "Pull member repair"
      end
      return "Host may pull"
    elseif comparison == "diverged" then
      if isHost then
        return candidate.precisePreviewReady and "Preview precise" or "Preview first"
      end
      return "Review with host"
    elseif comparison == "different_lineage" then
      return "Full snapshot"
    end
    return "Inspect"
  end

  local function BuildSummaryText(candidates, isHost, syncStatus)
    local counts = {
      same = 0,
      local_newer = 0,
      remote_newer = 0,
      diverged = 0,
      different_lineage = 0,
    }
    local bootstrapCounts = {
      ready = 0,
      pending = 0,
      review = 0,
      lineage = 0,
      unknown = 0,
    }
    for _, candidate in ipairs(type(candidates) == "table" and candidates or {}) do
      local comparison = string.lower(TrimText(candidate.comparison))
      if counts[comparison] ~= nil then
        counts[comparison] = counts[comparison] + 1
      end
      local bootstrapState = GetBootstrapState(candidate)
      if bootstrapCounts[bootstrapState] ~= nil then
        bootstrapCounts[bootstrapState] = bootstrapCounts[bootstrapState] + 1
      else
        bootstrapCounts.unknown = bootstrapCounts.unknown + 1
      end
    end
    syncStatus = type(syncStatus) == "table" and syncStatus or {}
    if (type(candidates) ~= "table") or #candidates == 0 then
      return "No joined member comparisons are available yet. Accept joins first, then refresh the difference table."
    end
    if isHost then
      return string.format(
        "%d roll-ready, %d waiting bootstrap, %d need review, %d aligned, %d need host repair, %d newer member copies, %d conflicts, %d lineage mismatch(es).",
        bootstrapCounts.ready,
        bootstrapCounts.pending,
        bootstrapCounts.review,
        counts.same,
        counts.local_newer,
        counts.remote_newer,
        counts.diverged,
        counts.different_lineage
      )
    end
    local summary = string.format(
      "%d roll-ready, %d waiting bootstrap, %d need review, %d aligned with your local copy, %d remote copies are newer, %d you are newer, %d conflicts, %d lineage mismatch(es).",
      bootstrapCounts.ready,
      bootstrapCounts.pending,
      bootstrapCounts.review,
      counts.same,
      counts.remote_newer,
      counts.local_newer,
      counts.diverged,
      counts.different_lineage
    )
    local message = TrimText(syncStatus.message)
    if message ~= "" then
      summary = summary .. "\nBackground: " .. message
    elseif counts.local_newer > 0 then
      summary = summary .. "\nBackground: wait for the session host to keep pushing the newer shared-profile scopes."
    elseif counts.remote_newer > 0 then
      summary = summary .. "\nBackground: your local copy looks newer for some scopes; the host can preview or pull them."
    end
    return summary
  end

  local function EnsureDashboard(editor)
    if not editor then
      return nil
    end
    if editor.lootSessionSyncDashboardFrame then
      return editor.lootSessionSyncDashboardFrame
    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 summaryText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    summaryText:SetJustifyH("LEFT")
    summaryText:SetJustifyV("TOP")
    frame.summaryText = summaryText

    frame.cards = {}
    for _ = 1, 6 do
      local card = CreateFrame("Frame", nil, frame, "BackdropTemplate")
      ApplyFrameSkin(card)
      if card.SetBackdropColor then
        card:SetBackdropColor(0.07, 0.08, 0.11, 0.96)
      end
      local countText = card:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
      countText:SetJustifyH("LEFT")
      local labelText = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      labelText:SetJustifyH("LEFT")
      local dot = card:CreateTexture(nil, "ARTWORK")
      dot:SetSize(8, 8)
      card.countText = countText
      card.labelText = labelText
      card.dot = dot
      frame.cards[#frame.cards + 1] = card
    end

    local matrixLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    matrixLabel:SetJustifyH("LEFT")
    matrixLabel:SetText("Joined Copy Matrix and Roll-Ready State")
    frame.matrixLabel = matrixLabel

    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

    local headerKeys = {
      { key = "member", text = "Member" },
      { key = "overall", text = "Overall" },
      { key = "ready", text = "Ready" },
      { key = "settings", text = "Settings" },
      { key = "permissions", text = "Permissions" },
      { key = "loot", text = "Loot" },
      { key = "session", text = "Session" },
      { key = "action", text = "Next Step" },
      { key = "updated", text = "Last Change" },
    }
    frame.headers = {}
    for _, headerDef in ipairs(headerKeys) do
      local text = columns:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      text:SetJustifyH("LEFT")
      text:SetText(headerDef.text)
      frame.headers[headerDef.key] = text
    end

    local rowsFrame = CreateFrame("Frame", nil, frame)
    frame.rowsFrame = rowsFrame
    frame.rows = {}

    local emptyText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    emptyText:SetJustifyH("LEFT")
    emptyText:SetJustifyV("TOP")
    emptyText:SetText("No joined member profile comparisons are available yet.")
    frame.emptyText = emptyText

    editor.lootSessionSyncDashboardFrame = frame
    return frame
  end

  local function EnsureRows(editor, frame, count)
    frame.rows = frame.rows or {}
    while #frame.rows < count do
      local row = CreateFrame("Frame", nil, frame.rowsFrame, "BackdropTemplate")
      ApplyFrameSkin(row)
      if row.SetBackdropColor then
        row:SetBackdropColor(0.06, 0.07, 0.10, 0.96)
      end
      row:EnableMouse(true)
      row:SetScript("OnMouseUp", function(self)
        local candidate = self.__candidate
        if type(candidate) == "table" then
          editor.lootSessionSelectedSyncMember = TrimText(candidate.memberKey)
          if type(OpenProfileSyncDetail) == "function" then
            OpenProfileSyncDetail(editor, candidate)
          end
        end
      end)

      row.memberText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.memberText:SetJustifyH("LEFT")
      row.overallText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.overallText:SetJustifyH("LEFT")
      row.readyText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.readyText:SetJustifyH("LEFT")
      row.settingsText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.settingsText:SetJustifyH("LEFT")
      row.permissionsText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.permissionsText:SetJustifyH("LEFT")
      row.lootText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.lootText:SetJustifyH("LEFT")
      row.sessionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.sessionText:SetJustifyH("LEFT")
      row.actionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.actionText:SetJustifyH("LEFT")
      row.updatedText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.updatedText:SetJustifyH("LEFT")
      frame.rows[#frame.rows + 1] = row
    end
  end

  local function RefreshSyncDashboard(editor, model)
    local frame = EnsureDashboard(editor)
    if not frame then
      return
    end

    model = type(model) == "table" and model or {}
    local candidates = type(model.candidates) == "table" and model.candidates or {}
    local isHost = model.isHost and true or false
    frame.summaryText:SetText(BuildSummaryText(candidates, isHost, model.syncStatus))

    local cards = {
      { label = "Roll Ready", count = 0, color = "ready" },
      { label = "Aligned", count = 0, color = "same" },
      { label = isHost and "Host Repair" or "You Newer", count = 0, color = "local_newer" },
      { label = isHost and "Member Repair" or "Remote Newer", count = 0, color = "remote_newer" },
      { label = "Conflicts", count = 0, color = "diverged" },
      { label = "Lineage", count = 0, color = "different_lineage" },
    }

    for _, candidate in ipairs(candidates) do
      if GetBootstrapState(candidate) == "ready" then
        cards[1].count = cards[1].count + 1
      end
      local comparison = string.lower(TrimText(candidate.comparison))
      if comparison == "same" then
        cards[2].count = cards[2].count + 1
      elseif comparison == "local_newer" then
        cards[3].count = cards[3].count + 1
      elseif comparison == "remote_newer" then
        cards[4].count = cards[4].count + 1
      elseif comparison == "diverged" then
        cards[5].count = cards[5].count + 1
      elseif comparison == "different_lineage" then
        cards[6].count = cards[6].count + 1
      end
    end

    for index, card in ipairs(frame.cards or {}) do
      local data = cards[index] or {}
      local r, g, b = GetStatusColor(data.color)
      if card.countText then
        card.countText:SetText(tostring(data.count or 0))
        card.countText:SetTextColor(r, g, b, 1.00)
      end
      if card.labelText then
        card.labelText:SetText(tostring(data.label or ""))
        card.labelText:SetTextColor(0.86, 0.88, 0.94, 1.00)
      end
      if card.dot and card.dot.SetColorTexture then
        card.dot:SetColorTexture(r, g, b, 1.00)
      end
      if card.SetBackdropBorderColor then
        card:SetBackdropBorderColor(r * 0.8, g * 0.8, b * 0.8, 0.9)
      end
    end

    EnsureRows(editor, frame, #candidates)
    for index, row in ipairs(frame.rows or {}) do
      local candidate = candidates[index]
      if candidate then
        row.__candidate = candidate
        local classR, classG, classB = GetSessionClassColor(candidate.classFile)
        local overallColorKey = string.lower(TrimText(candidate.comparison))
        local overallR, overallG, overallB = GetStatusColor(overallColorKey)
        local memberLabel = tostring(candidate.memberKey or "-")
        if candidate.isSessionHost then
          memberLabel = memberLabel .. " (Host)"
        end
        row.memberText:SetText(memberLabel)
        row.memberText:SetTextColor(classR, classG, classB, 1.00)
        row.overallText:SetText(GetComparisonLabel(candidate.comparison, isHost))
        row.overallText:SetTextColor(overallR, overallG, overallB, 1.00)
        row.readyText:SetText(GetBootstrapLabel(GetBootstrapState(candidate)))
        do
          local readyR, readyG, readyB = GetStatusColor(GetBootstrapState(candidate))
          row.readyText:SetTextColor(readyR, readyG, readyB, 1.00)
        end

        for _, domain in ipairs(DOMAIN_ORDER) do
          local label = GetDomainLabel(GetDomainState(candidate, domain))
          local r, g, b = GetStatusColor(GetDomainState(candidate, domain))
          local field = row[domain .. "Text"]
          if field then
            field:SetText(label)
            field:SetTextColor(r, g, b, 1.00)
          end
        end

        row.actionText:SetText(GetActionLabel(candidate, isHost))
        row.actionText:SetTextColor(0.86, 0.88, 0.94, 1.00)
        row.updatedText:SetText((tonumber(candidate.updatedAt) or 0) > 0 and FormatDateTime(candidate.updatedAt) or "-")
        row.updatedText:SetTextColor(0.76, 0.80, 0.88, 1.00)

        local selected = string.lower(TrimText(editor.lootSessionSelectedSyncMember)) == string.lower(TrimText(candidate.memberKey))
        if row.SetBackdropBorderColor then
          if selected then
            row:SetBackdropBorderColor(1.00, 0.82, 0.32, 1.00)
          else
            row:SetBackdropBorderColor(overallR * 0.75, overallG * 0.75, overallB * 0.75, 0.75)
          end
        end
        row:Show()
      else
        row.__candidate = nil
        row:Hide()
      end
    end

    frame.__rowCount = #candidates
    if #candidates == 0 then
      frame.emptyText:Show()
      frame.rowsFrame:Hide()
    else
      frame.emptyText:Hide()
      frame.rowsFrame:Show()
    end
    frame.__rowsHeight = math.max(28, #candidates * 28)
  end

  local function LayoutSyncDashboard(editor, topOffset, rightContentWidth)
    local frame = EnsureDashboard(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.summaryText:ClearAllPoints()
    frame.summaryText:SetPoint("TOPLEFT", 14, -14)
    frame.summaryText:SetWidth(width - 28)
    local summaryHeight = math.max(34, math.ceil(frame.summaryText:GetStringHeight()))

    local cardsPerRow = width >= 760 and 5 or (width >= 620 and 4 or (width >= 480 and 3 or 2))
    local cardGap = 10
    local cardWidth = math.floor((width - 28 - ((cardsPerRow - 1) * cardGap)) / cardsPerRow)
    cardWidth = math.max(112, cardWidth)
    local cardHeight = 56
    local cardsTop = -14 - summaryHeight - 12
    local cardRows = 0
    for index, card in ipairs(frame.cards or {}) do
      local rowIndex = math.floor((index - 1) / cardsPerRow)
      local columnIndex = (index - 1) % cardsPerRow
      local x = 14 + (columnIndex * (cardWidth + cardGap))
      local y = cardsTop - (rowIndex * (cardHeight + 8))
      card:ClearAllPoints()
      card:SetPoint("TOPLEFT", x, y)
      card:SetSize(cardWidth, cardHeight)
      card.countText:ClearAllPoints()
      card.countText:SetPoint("TOPLEFT", 10, -10)
      card.labelText:ClearAllPoints()
      card.labelText:SetPoint("TOPLEFT", 10, -34)
      card.dot:ClearAllPoints()
      card.dot:SetPoint("TOPRIGHT", -10, -12)
      cardRows = math.max(cardRows, rowIndex + 1)
    end
    local cardsHeight = math.max(0, cardRows * cardHeight + math.max(0, cardRows - 1) * 8)

    local matrixTop = cardsTop - cardsHeight - 18
    frame.matrixLabel:ClearAllPoints()
    frame.matrixLabel:SetPoint("TOPLEFT", 14, matrixTop)

    local columnsTop = matrixTop - 26
    frame.columns:ClearAllPoints()
    frame.columns:SetPoint("TOPLEFT", 14, columnsTop)
    frame.columns:SetSize(width - 28, 22)

    local innerWidth = width - 28
    local memberWidth = math.max(122, math.floor(innerWidth * 0.18))
    local overallWidth = math.max(86, math.floor(innerWidth * 0.10))
    local readyWidth = math.max(72, math.floor(innerWidth * 0.08))
    local domainWidth = math.max(64, math.floor(innerWidth * 0.085))
    local actionWidth = math.max(104, math.floor(innerWidth * 0.15))
    local updatedWidth = math.max(92, innerWidth - memberWidth - overallWidth - readyWidth - (domainWidth * 4) - actionWidth - 18)

    local columnOffsets = {
      member = 8,
      overall = 8 + memberWidth,
      ready = 8 + memberWidth + overallWidth,
      settings = 8 + memberWidth + overallWidth + readyWidth,
      permissions = 8 + memberWidth + overallWidth + readyWidth + domainWidth,
      loot = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 2),
      session = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 3),
      action = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 4),
      updated = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 4) + actionWidth,
    }
    local columnWidths = {
      member = memberWidth,
      overall = overallWidth,
      ready = readyWidth,
      settings = domainWidth,
      permissions = domainWidth,
      loot = domainWidth,
      session = domainWidth,
      action = actionWidth,
      updated = updatedWidth,
    }

    for key, header in pairs(frame.headers or {}) do
      header:ClearAllPoints()
      header:SetPoint("TOPLEFT", columnOffsets[key] or 8, -4)
      header:SetWidth(columnWidths[key] or 80)
    end

    local rowsTop = columnsTop - 26
    frame.rowsFrame:ClearAllPoints()
    frame.rowsFrame:SetPoint("TOPLEFT", 14, rowsTop)
    frame.rowsFrame:SetSize(width - 28, math.max(1, tonumber(frame.__rowsHeight) or 28))

    for index, row in ipairs(frame.rows or {}) do
      row:ClearAllPoints()
      row:SetPoint("TOPLEFT", 0, -((index - 1) * 28))
      row:SetSize(width - 28, 24)
      for key, offset in pairs(columnOffsets) do
        local field = row[key .. "Text"]
        if field then
          field:ClearAllPoints()
          field:SetPoint("TOPLEFT", offset, -4)
          field:SetWidth(columnWidths[key] or 80)
        end
      end
    end

    frame.emptyText:ClearAllPoints()
    frame.emptyText:SetPoint("TOPLEFT", 14, rowsTop)
    frame.emptyText:SetWidth(width - 28)
    local emptyHeight = math.max(28, math.ceil(frame.emptyText:GetStringHeight()))
    local tableHeight = (tonumber(frame.__rowCount) or 0) > 0 and (tonumber(frame.__rowsHeight) or 28) or emptyHeight
    local totalHeight = math.abs(rowsTop) + tableHeight + 16
    frame:SetHeight(totalHeight)
    return totalHeight
  end

  return {
    RefreshSyncDashboard = RefreshSyncDashboard,
    LayoutSyncDashboard = LayoutSyncDashboard,
  }
end
