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 function RequireApi(name, value)
  if value == nil then
    value = rawget(_G, name)
  end
  if value == nil then
    local api = rawget(_G, "LIDCOUNCIL_API")
    if type(api) == "table" then
      value = api[name]
    end
  end
  if value == nil then
    error("LidCouncil missing API: " .. tostring(name))
  end
  return value
end

local RootDB = RequireApi("LIDCOUNCIL_RootDB", _G.LIDCOUNCIL_RootDB)
local DB = RequireApi("LIDCOUNCIL_DB", _G.LIDCOUNCIL_DB)
local EnsureDefaults = RequireApi("LIDCOUNCIL_EnsureDefaults", _G.LIDCOUNCIL_EnsureDefaults)
local Print = RequireApi("LIDCOUNCIL_Print", _G.LIDCOUNCIL_Print)
local RebuildAllPanels = RequireApi("LIDCOUNCIL_RebuildAllPanels", _G.LIDCOUNCIL_RebuildAllPanels)
local CopyLootProfile = RequireApi("LIDCOUNCIL_CopyLootProfile", _G.LIDCOUNCIL_CopyLootProfile)
local DeleteLootProfile = RequireApi("LIDCOUNCIL_DeleteLootProfile", _G.LIDCOUNCIL_DeleteLootProfile)
local GetEnabledLootProfileName = RequireApi("LIDCOUNCIL_GetEnabledLootProfileName", _G.LIDCOUNCIL_GetEnabledLootProfileName)
local LogSilent = rawget(_G, "LIDCOUNCIL_LogSilent")

local MAX_PROFILE_NAME = 25
local MAX_PROFILE_GROUP_NAME = 25

local function GetEditor()
  local value = rawget(_G, "LIDCOUNCIL_Editor")
  if type(value) == "table" and value.__lidCouncilEditor then
    return value
  end
  return nil
end

local function TrimName(name)
  if not name then
    return ""
  end
  name = tostring(name)
  name = name:gsub("^%s+", ""):gsub("%s+$", "")
  return name
end

local function ClampNameLen(name, maxLen, label)
  if not name then
    return ""
  end
  if #name > maxLen then
    local trimmed = string.sub(name, 1, maxLen)
    Print(label .. " name too long. Trimmed to: " .. trimmed)
    if LogSilent then
      LogSilent(label .. " name trimmed to: " .. trimmed)
    end
    return trimmed
  end
  return name
end

local function IsReservedSimulationProfileName(name)
  return string.lower(tostring(name or "")) == "simulation"
end

local function SwitchProfile(name)
  if not name or name == "" then
    return
  end
  local root = RootDB()
  root.currentProfile = name
  root.ui = root.ui or {}
  if tostring(name) ~= "Simulation" and tostring(name) ~= "Default" then
    root.ui.lastNonSimulationProfile = name
  end
  if type(INTERNAL_API.LIDCOUNCIL_ClearInactiveCombatDeferredState_Impl) == "function" then
    INTERNAL_API.LIDCOUNCIL_ClearInactiveCombatDeferredState_Impl()
  end
  DB()
end

local profileGroupsNormalizedRoot
local profileGroupMembershipRoot
local profileGroupMembershipByProfile
local sortedProfileNamesRoot
local sortedProfileNamesCache
local sortedProfileNamesDeveloperMode
local sortedProfileNamesShowSimulation

local function IsSimulationProfileVisible(root)
  root = root or RootDB()
  local ui = type(root.ui) == "table" and root.ui or nil
  return type(root.profiles) == "table"
    and type(root.profiles.Simulation) == "table"
    and type(ui) == "table"
    and ui.developerMode == true
    and ui.showSimulationProfile == true
end

local function InvalidateSortedProfileNames(root)
  if not root or sortedProfileNamesRoot == root then
    sortedProfileNamesRoot = nil
    sortedProfileNamesCache = nil
    sortedProfileNamesDeveloperMode = nil
    sortedProfileNamesShowSimulation = nil
  end
end

local function ListProfiles()
  local root = RootDB()
  local ui = type(root.ui) == "table" and root.ui or {}
  local developerMode = ui.developerMode == true
  local showSimulationProfile = ui.showSimulationProfile == true
  if sortedProfileNamesRoot == root and type(sortedProfileNamesCache) == "table" then
    if sortedProfileNamesDeveloperMode == developerMode and sortedProfileNamesShowSimulation == showSimulationProfile then
      return sortedProfileNamesCache
    end
  end

  local names = {}
  for profileName in pairs(root.profiles or {}) do
    if profileName ~= "Default" and (profileName ~= "Simulation" or IsSimulationProfileVisible(root)) then
      names[#names + 1] = profileName
    end
  end
  table.sort(names)
  sortedProfileNamesRoot = root
  sortedProfileNamesCache = names
  sortedProfileNamesDeveloperMode = developerMode
  sortedProfileNamesShowSimulation = showSimulationProfile
  return names
end

local function EnsureProfileGroups()
  local root = RootDB()
  root.profileGroups = root.profileGroups or {}
  if profileGroupsNormalizedRoot == root and profileGroupMembershipRoot == root and type(root.profileGroups) == "table" then
    return root.profileGroups
  end

  local groups = root.profileGroups
  local normalized = {}
  local seenNames = {}
  local membership = {}

  local function SortNames(list)
    table.sort(list, function(a, b)
      return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
    end)
  end

  for _, group in ipairs(groups) do
    if type(group) == "table" then
      local rawName = tostring(group.name or "")
      local name = ClampNameLen(TrimName(rawName), MAX_PROFILE_GROUP_NAME, "Profile group")
      local key = string.lower(name or "")
      if name ~= "" and not seenNames[key] then
        seenNames[key] = true
        local clean = {
          name = name,
          collapsed = group.collapsed == true,
          profiles = {},
        }
        local seenProfiles = {}
        for _, profileName in ipairs(group.profiles or {}) do
          local normalizedProfileName = tostring(profileName or "")
          if normalizedProfileName ~= "" and normalizedProfileName ~= "Default" and root.profiles[normalizedProfileName] and not seenProfiles[normalizedProfileName] then
            clean.profiles[#clean.profiles + 1] = normalizedProfileName
            seenProfiles[normalizedProfileName] = true
            membership[normalizedProfileName] = name
          end
        end
        SortNames(clean.profiles)
        normalized[#normalized + 1] = clean
      end
    end
  end

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

  root.profileGroups = normalized
  profileGroupsNormalizedRoot = root
  profileGroupMembershipRoot = root
  profileGroupMembershipByProfile = membership
  return normalized
end

local function FindProfileGroupIndex(groups, groupName)
  local needle = string.lower(tostring(groupName or ""))
  for index, group in ipairs(groups or {}) do
    if string.lower(tostring(group.name or "")) == needle then
      return index
    end
  end
  return nil
end

local function GetProfileGroupName(profileName)
  if not profileName or profileName == "" then
    return nil
  end
  local root = RootDB()
  if profileGroupMembershipRoot ~= root then
    EnsureProfileGroups()
  end
  return profileGroupMembershipByProfile and profileGroupMembershipByProfile[profileName] or nil
end

local function RemoveProfileFromAllGroups(profileName, groups)
  if not profileName or profileName == "" then
    return nil
  end
  groups = groups or EnsureProfileGroups()
  local root = RootDB()
  local removedFrom = nil
  for _, group in ipairs(groups) do
    for index = #group.profiles, 1, -1 do
      if group.profiles[index] == profileName then
        removedFrom = group.name
        table.remove(group.profiles, index)
      end
    end
  end
  if profileGroupMembershipRoot == root and profileGroupMembershipByProfile then
    profileGroupMembershipByProfile[profileName] = nil
  end
  return removedFrom
end

local function AssignProfileToGroup(profileName, groupName)
  if not profileName or profileName == "" then
    return nil
  end
  local groups = EnsureProfileGroups()
  local root = RootDB()
  if not root.profiles[profileName] or profileName == "Default" then
    return nil
  end

  if not groupName or groupName == "" then
    RemoveProfileFromAllGroups(profileName, groups)
    profileGroupsNormalizedRoot = root
    return true
  end

  local index = FindProfileGroupIndex(groups, groupName)
  if not index then
    return nil
  end

  RemoveProfileFromAllGroups(profileName, groups)

  local group = groups[index]
  group.collapsed = false
  group.profiles = group.profiles or {}
  group.profiles[#group.profiles + 1] = profileName
  table.sort(group.profiles, function(a, b)
    return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
  end)
  profileGroupsNormalizedRoot = root
  if profileGroupMembershipRoot == root and profileGroupMembershipByProfile then
    profileGroupMembershipByProfile[profileName] = group.name
  end
  return true
end

local function ToggleProfileGroupCollapsed(groupName)
  local groups = EnsureProfileGroups()
  local index = FindProfileGroupIndex(groups, groupName)
  if not index then
    return nil
  end
  groups[index].collapsed = not groups[index].collapsed
  return true
end

local function CreateProfileGroupNamed(rawName)
  local name = ClampNameLen(TrimName(rawName), MAX_PROFILE_GROUP_NAME, "Profile group")
  if name == "" then
    Print("Please enter a group name.")
    return nil
  end

  local groups = EnsureProfileGroups()
  if FindProfileGroupIndex(groups, name) then
    Print("Profile group already exists: " .. name)
    return nil
  end

  groups[#groups + 1] = {
    name = name,
    collapsed = false,
    profiles = {},
  }
  table.sort(groups, function(a, b)
    return string.lower(tostring((a and a.name) or "")) < string.lower(tostring((b and b.name) or ""))
  end)
  profileGroupsNormalizedRoot = RootDB()

  local editor = GetEditor()
  if editor then
    editor.selectedProfileGroupName = name
    editor.selectedLootProfileGroupName = name
    editor.__lastLootProfilePageFocus = nil
    if editor.RefreshLootProfilePage then
      editor:RefreshLootProfilePage()
    elseif editor.RefreshProfilesSection then
      editor:RefreshProfilesSection()
    elseif editor.Refresh then
      editor:Refresh()
    end
  end
  return name
end

local function RenameProfileGroup(oldName, rawNewName)
  local name = ClampNameLen(TrimName(rawNewName), MAX_PROFILE_GROUP_NAME, "Profile group")
  if not oldName or oldName == "" then
    return false
  end
  if name == "" then
    Print("Please enter a group name.")
    return false
  end

  local groups = EnsureProfileGroups()
  local index = FindProfileGroupIndex(groups, oldName)
  if not index then
    return false
  end

  local existingIndex = FindProfileGroupIndex(groups, name)
  if existingIndex and existingIndex ~= index then
    Print("Profile group already exists: " .. name)
    return false
  end

  local renamedGroup = groups[index]
  renamedGroup.name = name
  table.sort(groups, function(a, b)
    return string.lower(tostring((a and a.name) or "")) < string.lower(tostring((b and b.name) or ""))
  end)
  profileGroupsNormalizedRoot = RootDB()
  if profileGroupMembershipRoot == RootDB() and profileGroupMembershipByProfile then
    for _, profileName in ipairs(renamedGroup.profiles or {}) do
      profileGroupMembershipByProfile[profileName] = name
    end
  end

  local editor = GetEditor()
  if editor and editor.selectedProfileGroupName == oldName then
    editor.selectedProfileGroupName = name
  end
  if editor and editor.selectedLootProfileGroupName == oldName then
    editor.selectedLootProfileGroupName = name
  end
  if editor and editor.RefreshLootProfilePage then
    editor:RefreshLootProfilePage()
  elseif editor and editor.RefreshProfilesSection then
    editor:RefreshProfilesSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return name
end

local function DeleteProfileGroup(groupName)
  if not groupName or groupName == "" then
    return false
  end
  local groups = EnsureProfileGroups()
  local index = FindProfileGroupIndex(groups, groupName)
  if not index then
    return false
  end

  if profileGroupMembershipRoot == RootDB() and profileGroupMembershipByProfile then
    for _, profileName in ipairs(groups[index].profiles or {}) do
      profileGroupMembershipByProfile[profileName] = nil
    end
  end
  table.remove(groups, index)
  profileGroupsNormalizedRoot = RootDB()

  local editor = GetEditor()
  if editor and editor.selectedProfileGroupName == groupName then
    editor.selectedProfileGroupName = nil
  end
  if editor and editor.selectedLootProfileGroupName == groupName then
    editor.selectedLootProfileGroupName = nil
  end
  if editor and editor.RefreshLootProfilePage then
    editor:RefreshLootProfilePage()
  elseif editor and editor.RefreshProfilesSection then
    editor:RefreshProfilesSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

local function CopyProfileFrom(sourceName, newName)
  if not sourceName or sourceName == "" then
    return false
  end
  if not newName or newName == "" then
    return false
  end
  newName = ClampNameLen(TrimName(newName), MAX_PROFILE_NAME, "Profile")
  if newName == "" then
    return false
  end
  if IsReservedSimulationProfileName(newName) then
    Print("The Simulation profile name is reserved for Developer Mode.")
    return false
  end

  local root = RootDB()
  if root.profiles[newName] then
    Print("Profile already exists: " .. newName)
    return false
  end

  local ok, result = CopyLootProfile(sourceName, newName)
  if not ok then
    Print(tostring(result or ("Could not copy profile: " .. tostring(sourceName))))
    return false
  end

  InvalidateSortedProfileNames(root)
  local sourceGroup = GetProfileGroupName(sourceName)
  if sourceGroup then
    AssignProfileToGroup(newName, sourceGroup)
  end
  SwitchProfile(newName)
  EnsureDefaults()

  local editor = GetEditor()
  if editor then
    editor.selectedProfileName = newName
    editor.selectedLootProfileName = newName
    editor.selectedProfileGroupName = nil
    editor.selectedLootProfileGroupName = nil
    if editor.RefreshLootProfilePage then
      editor:RefreshLootProfilePage()
    elseif editor.RefreshProfilesSection then
      editor:RefreshProfilesSection()
    elseif editor.Refresh then
      editor:Refresh()
    end
  end
  return newName
end

local function DeleteProfile(name)
  if not name or name == "" then
    return false, "Profile name is required."
  end
  if name == "Default" then
    Print("Cannot delete Default profile.")
    return false, "Cannot delete Default profile."
  end
  if TrimName(GetEnabledLootProfileName() or "") == name then
    local message = "Cannot delete an activated profile. Please deactivate it first."
    Print(message)
    return false, message
  end
  local ok, message = DeleteLootProfile(name)
  if not ok then
    if TrimName(message) ~= "" then
      Print(tostring(message))
    end
    return false, message
  end
  local root = RootDB()
  RemoveProfileFromAllGroups(name)
  InvalidateSortedProfileNames(root)
  if root.currentProfile == name then
    root.currentProfile = "Default"
  end
  DB()
  EnsureDefaults()
  local editor = GetEditor()
  if editor then
    if editor.selectedProfileName == name then
      editor.selectedProfileName = nil
    end
    if editor.selectedLootProfileName == name then
      editor.selectedLootProfileName = nil
    end
  end
  return true, message
end

local function CreateProfileNamed(name)
  local newName = ClampNameLen(TrimName(name), MAX_PROFILE_NAME, "Profile")
  if newName == "" then
    Print("Please enter a profile name.")
    return false
  end
  if newName == "Default" then
    Print("Cannot create profile named Default.")
    return false
  end
  if IsReservedSimulationProfileName(newName) then
    Print("The Simulation profile is created from Developer Mode.")
    return false
  end

  local root = RootDB()
  if root.profiles[newName] then
    Print("Profile already exists: " .. newName)
    return false
  end

  root.profiles[newName] = {
    panels = {},
    positions = {},
    spec = { auto = false, layouts = {} },
    __seeded = true,
    profileSpec = { enabled = false, mode = "SPEC", specID = nil, character = nil },
  }
  InvalidateSortedProfileNames(root)

  SwitchProfile(newName)
  EnsureDefaults()
  RebuildAllPanels()

  local editor = GetEditor()
  if editor then
    editor.selectedProfileName = newName
    editor.selectedLootProfileName = newName
    editor.selectedProfileGroupName = nil
    editor.selectedLootProfileGroupName = nil
    if editor.RefreshLootProfilePage then
      editor:RefreshLootProfilePage()
    elseif editor.RefreshProfilesSection then
      editor:RefreshProfilesSection()
    elseif editor.Refresh then
      editor:Refresh()
    end
  end
  return true
end

local function RenameProfile(oldName, newName)
  if not oldName or oldName == "" then
    return false
  end
  newName = ClampNameLen(TrimName(newName), MAX_PROFILE_NAME, "Profile")
  if not newName or newName == "" then
    return false
  end
  if oldName == newName then
    return true, oldName
  end

  local root = RootDB()
  if root.profiles[newName] then
    Print("Profile already exists: " .. newName)
    return false
  end
  if oldName == "Default" then
    Print("Cannot rename Default profile.")
    return false
  end
  if IsReservedSimulationProfileName(oldName) or IsReservedSimulationProfileName(newName) then
    Print("The Simulation profile name is reserved for Developer Mode.")
    return false
  end

  root.profiles[newName] = root.profiles[oldName]
  root.profiles[oldName] = nil
  InvalidateSortedProfileNames(root)

  local groups = EnsureProfileGroups()
  for _, group in ipairs(groups) do
    for index, profileName in ipairs(group.profiles or {}) do
      if profileName == oldName then
        group.profiles[index] = newName
      end
    end
    table.sort(group.profiles, function(a, b)
      return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
    end)
  end
  profileGroupsNormalizedRoot = root
  if profileGroupMembershipRoot == root and profileGroupMembershipByProfile then
    profileGroupMembershipByProfile[newName] = profileGroupMembershipByProfile[oldName]
    profileGroupMembershipByProfile[oldName] = nil
  end
  if root.currentProfile == oldName then
    root.currentProfile = newName
  end
  return true, newName
end

local function BuildVisibleProfileRows(root, selectedProfileName)
  local rows = {}
  local grouped = {}
  local currentProfile = root and root.currentProfile or nil
  local groups = EnsureProfileGroups()

  for _, group in ipairs(groups) do
    local childNames = {}
    for _, profileName in ipairs(group.profiles or {}) do
      if root.profiles[profileName] and profileName ~= "Default" and (profileName ~= "Simulation" or IsSimulationProfileVisible(root)) then
        childNames[#childNames + 1] = profileName
        grouped[profileName] = group.name
      end
    end

    local containsCurrent = false
    local containsSelected = false
    for _, profileName in ipairs(childNames) do
      if profileName == currentProfile then
        containsCurrent = true
      end
      if profileName == selectedProfileName then
        containsSelected = true
      end
    end

    rows[#rows + 1] = {
      type = "group",
      key = "group:" .. string.lower(group.name or ""),
      name = group.name,
      collapsed = group.collapsed == true,
      childCount = #childNames,
      containsCurrent = containsCurrent,
      containsSelected = containsSelected,
    }

    if group.collapsed ~= true then
      for _, profileName in ipairs(childNames) do
        rows[#rows + 1] = {
          type = "profile",
          key = "profile:" .. profileName,
          name = profileName,
          groupName = group.name,
        }
      end
    end
  end

  for _, profileName in ipairs(ListProfiles()) do
    if not grouped[profileName] then
      rows[#rows + 1] = {
        type = "profile",
        key = "profile:" .. profileName,
        name = profileName,
        groupName = nil,
      }
    end
  end

  return rows
end

local function GetProfileFocusKey(selectedProfileName, selectedGroupName)
  if selectedGroupName and selectedGroupName ~= "" then
    return "group:" .. string.lower(selectedGroupName)
  end
  if not selectedProfileName or selectedProfileName == "" then
    return nil
  end

  local groupName = GetProfileGroupName(selectedProfileName)
  if groupName then
    local groups = EnsureProfileGroups()
    local index = FindProfileGroupIndex(groups, groupName)
    if index and groups[index] and groups[index].collapsed then
      return "group:" .. string.lower(groupName)
    end
  end
  return "profile:" .. selectedProfileName
end

INTERNAL_API.LIDCOUNCIL_EditorProfileManagement_Impl = {
  InvalidateSortedProfileNames = InvalidateSortedProfileNames,
  SwitchProfile = SwitchProfile,
  ListProfiles = ListProfiles,
  EnsureProfileGroups = EnsureProfileGroups,
  FindProfileGroupIndex = FindProfileGroupIndex,
  GetProfileGroupName = GetProfileGroupName,
  RemoveProfileFromAllGroups = RemoveProfileFromAllGroups,
  AssignProfileToGroup = AssignProfileToGroup,
  ToggleProfileGroupCollapsed = ToggleProfileGroupCollapsed,
  CreateProfileGroupNamed = CreateProfileGroupNamed,
  RenameProfileGroup = RenameProfileGroup,
  DeleteProfileGroup = DeleteProfileGroup,
  CopyProfileFrom = CopyProfileFrom,
  DeleteProfile = DeleteProfile,
  CreateProfileNamed = CreateProfileNamed,
  RenameProfile = RenameProfile,
  BuildVisibleProfileRows = BuildVisibleProfileRows,
  GetProfileFocusKey = GetProfileFocusKey,
}
