/*
 *  Copyright (C) 2017-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "AddonSettings.h"

#include "FileItem.h"
#include "FileItemList.h"
#include "GUIInfoManager.h"
#include "LangInfo.h"
#include "ServiceBroker.h"
#include "addons/addoninfo/AddonInfo.h"
#include "addons/addoninfo/AddonType.h"
#include "addons/binary-addons/AddonInstanceHandler.h"
#include "addons/gui/GUIDialogAddonSettings.h"
#include "addons/settings/SettingUrlEncodedString.h"
#include "filesystem/Directory.h"
#include "guilib/GUIComponent.h"
#include "messaging/ApplicationMessenger.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
#include "settings/SettingAddon.h"
#include "settings/SettingConditions.h"
#include "settings/SettingControl.h"
#include "settings/SettingDateTime.h"
#include "settings/SettingPath.h"
#include "settings/lib/Setting.h"
#include "settings/lib/SettingDefinitions.h"
#include "settings/lib/SettingSection.h"
#include "settings/lib/SettingsManager.h"
#include "utils/FileExtensionProvider.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/XBMCTinyXML.h"
#include "utils/XMLUtils.h"
#include "utils/log.h"

#include <algorithm>
#include <cassert>
#include <memory>
#include <mutex>
#include <vector>

namespace
{

constexpr const char* OldSettingValuesSeparator = "|";

constexpr int UnknownSettingLabelIdStart = 100000;

bool InfoBool(const std::string& /*condition*/,
              const std::string& value,
              const SettingConstPtr& /*setting*/)
{
  return CServiceBroker::GetGUI()->GetInfoManager().EvaluateBool(value, INFO::DEFAULT_CONTEXT);
}

template<class TSetting>
SettingPtr InitializeFromOldSettingWithoutDefinition(ADDON::CAddonSettings& settings,
                                                     const std::string& settingId,
                                                     const typename TSetting::Value& defaultValue)
{
  const auto setting{std::make_shared<TSetting>(settingId, settings.GetSettingsManager())};
  setting->SetLevel(SettingLevel::Internal);
  setting->SetVisible(false);
  setting->SetDefault(defaultValue);

  return setting;
}

template<>
SettingPtr InitializeFromOldSettingWithoutDefinition<CSettingString>(
    ADDON::CAddonSettings& settings,
    const std::string& settingId,
    const typename CSettingString::Value& defaultValue)
{
  const auto setting{std::make_shared<CSettingString>(settingId, settings.GetSettingsManager())};
  setting->SetLevel(SettingLevel::Internal);
  setting->SetVisible(false);
  setting->SetDefault(defaultValue);
  setting->SetAllowEmpty(true);

  return setting;
}

template<class TSetting>
SettingPtr AddSettingWithoutDefinition(ADDON::CAddonSettings& settings,
                                       const std::string& settingId,
                                       const typename TSetting::Value& defaultValue,
                                       const Logger& logger)
{
  if (settingId.empty())
    return nullptr;

  // if necessary try to initialize the settings manager on-the-fly without any definitions
  if (!settings.IsInitialized() && !settings.Initialize(CXBMCTinyXML(), true))
  {
    logger->warn("failed to initialize settings on-the-fly");
    return nullptr;
  }

  // check if we need to add a section on-the-fly
  const SettingSectionList sections{settings.GetSettingsManager()->GetSections()};
  SettingSectionPtr section;
  if (sections.empty())
    section =
        std::make_shared<CSettingSection>(settings.GetAddonId(), settings.GetSettingsManager());
  else
    section = sections.back();

  // check if we need to add a category on-the-fly
  const SettingCategoryList& categories{section->GetCategories()};
  SettingCategoryPtr category;
  if (categories.empty())
    category = std::make_shared<CSettingCategory>("category0", settings.GetSettingsManager());
  else
    category = categories.back();

  // check if we need to add a group on-the-fly
  const SettingGroupList& groups{category->GetGroups()};
  SettingGroupPtr group;
  if (groups.empty())
    group = std::make_shared<CSettingGroup>("0", settings.GetSettingsManager());
  else
    group = groups.back();

  // create a new setting on-the-fly
  SettingPtr setting{
      InitializeFromOldSettingWithoutDefinition<TSetting>(settings, settingId, defaultValue)};
  if (!setting)
  {
    logger->warn(R"(failed to create setting "{}" on-the-fly)", settingId);
    return nullptr;
  }

  // add the setting (and if necessary the section, category and/or group)
  if (!settings.GetSettingsManager()->AddSetting(setting, section, category, group))
  {
    logger->warn(R"(failed to add setting "{}" on-the-fly)", settingId);
    return nullptr;
  }

  return setting;
}

} // namespace

namespace ADDON
{

CAddonSettings::CAddonSettings(const std::shared_ptr<IAddon>& addon, AddonInstanceId instanceId)
  : CSettingsBase(),
    m_addonId(addon->ID()),
    m_addonPath(addon->Path()),
    m_addonProfile(addon->Profile()),
    m_instanceId(instanceId),
    m_addon{addon},
    m_unknownSettingLabelId(UnknownSettingLabelIdStart),
    m_logger(CServiceBroker::GetLogging().GetLogger(
        StringUtils::Format("CAddonSettings[{}@{}]", m_instanceId, m_addonId)))
{
}

std::shared_ptr<CSetting> CAddonSettings::CreateSetting(
    const std::string& settingType,
    const std::string& settingId,
    CSettingsManager* settingsManager /* = nullptr */) const
{
  if (StringUtils::EqualsNoCase(settingType, "urlencodedstring"))
    return std::make_shared<CSettingUrlEncodedString>(settingId, settingsManager);

  return CSettingCreator::CreateSetting(settingType, settingId, settingsManager);
}

void CAddonSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
{
  std::string actionData;
  bool closeDialog = false;

  // check if it's an action setting
  if (setting->GetType() == SettingType::Action)
  {
    const auto settingAction{std::dynamic_pointer_cast<const CSettingAction>(setting)};
    if (settingAction && settingAction->HasData())
    {
      actionData = settingAction->GetData();
      // replace $CWD with the url of the add-on
      StringUtils::Replace(actionData, "$CWD", m_addonPath);
      // replace $ID with the id of the add-on
      StringUtils::Replace(actionData, "$ID", m_addonId);
    }
  }

  // check if the setting control's is a button and its format is action
  if (setting->GetControl()->GetType() == "button" &&
      setting->GetControl()->GetFormat() == "action")
  {
    const auto controlButton{
        std::dynamic_pointer_cast<const CSettingControlButton>(setting->GetControl())};
    if (controlButton)
    {
      if (actionData.empty() && controlButton->HasActionData())
        actionData = controlButton->GetActionData();

      closeDialog = controlButton->CloseDialog();
    }
  }

  if (actionData.empty())
    return;

  if (closeDialog)
    CGUIDialogAddonSettings::SaveAndClose();

  CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, actionData);
}

bool CAddonSettings::AddInstanceSettings() const
{
  if (GetSetting(ADDON_SETTING_INSTANCE_NAME_VALUE) ||
      GetSetting(ADDON_SETTING_INSTANCE_ENABLED_VALUE))
  {
    CLog::LogF(LOGDEBUG, "Add-on {} using instance setting values byself, Kodi's add ignored",
               m_addonId);
    return true;
  }

  CSettingsManager* mgr{GetSettingsManager()};
  if (!mgr)
    return false;

  const SettingSectionList sections{mgr->GetSections()};
  if (sections.empty())
    return false;

  const SettingSectionPtr section{*sections.begin()};

  const SettingCategoryList& categories{section->GetCategories()};
  if (categories.empty())
    return false;

  const SettingCategoryPtr category{*categories.begin()};

  const SettingGroupList& groups{category->GetGroups()};
  const auto itr = std::ranges::find_if(groups, [](const SettingGroupPtr& group)
                                        { return group->GetId() == ADDON_SETTING_INSTANCE_GROUP; });

  SettingGroupPtr group;
  if (itr != groups.end())
  {
    group = *itr;
  }
  else
  {
    group = std::make_shared<CSettingGroup>(ADDON_SETTING_INSTANCE_GROUP, mgr);
    group->SetLabel(10017); // Add-on configuration
    category->AddGroupToFront(group);
  }

  const auto name{
      std::make_shared<CSettingString>(ADDON_SETTING_INSTANCE_NAME_VALUE, 551, "", mgr)}; // Name
  name->SetAllowEmpty(false);
  name->SetControl(std::make_shared<CSettingControlEdit>());
  if (!mgr->AddSetting(name, section, category, group))
    return false;

  const auto enabled{std::make_shared<CSettingBool>(ADDON_SETTING_INSTANCE_ENABLED_VALUE, 305, true,
                                                    mgr)}; // Enabled
  enabled->SetControl(std::make_shared<CSettingControlCheckmark>());
  if (!mgr->AddSetting(enabled, section, category, group))
    return false;

  return true;
}

bool CAddonSettings::Initialize(const CXBMCTinyXML& doc, bool allowEmpty /* = false */)
{
  std::unique_lock lock(m_critical);
  if (m_initialized)
    return false;

  // register custom setting types
  InitializeSettingTypes();
  // register custom setting controls
  InitializeControls();

  // conditions need to be initialized before the setting definitions
  InitializeConditions();

  // load the settings definitions
  if (!InitializeDefinitions(doc) && !allowEmpty)
    return false;

  // Add internal settings to set values about instance set
  if (m_instanceId > 0 && !AddInstanceSettings())
    return false;

  GetSettingsManager()->SetInitialized();

  m_initialized = true;

  return true;
}

bool CAddonSettings::Load(const CXBMCTinyXML& doc)
{
  std::unique_lock lock(m_critical);
  if (!m_initialized)
    return false;

  // figure out the version of the setting definitions
  uint32_t version = 0;
  if (!ParseSettingVersion(doc, version))
  {
    m_logger->error("failed to determine setting values version");
    return false;
  }

  std::map<std::string, std::string, std::less<>> settingValues;

  // for new/"normal" setting values use the standard process
  if (version != 0)
  {
    bool updated;
    if (!LoadValuesFromXml(doc, updated))
      return false;

    // helper lambda for parsing a setting's ID and value from XML
    const auto parseSettingValue =
        [&settingValues](const TiXmlNode* setting, const std::string& categoryId = "")
    {
      // put together the setting ID
      std::string settingId{categoryId};
      if (!settingId.empty())
        settingId += ".";
      auto id = setting->ToElement()->Attribute("id");
      if (id)
        settingId += id;

      // parse the setting value
      std::string settingValue;
      if (setting->FirstChild())
        settingValue = setting->FirstChild()->ValueStr();

      // add the setting to the map
      settingValues.try_emplace(settingId, settingValue);
    };

    // check if there were any setting values without a definition
    auto category{doc.RootElement()->FirstChild()};
    while (category)
    {
      // check if this really is a category with setting elements
      if (category->FirstChild() && category->FirstChild()->Type() == CXBMCTinyXML::TINYXML_ELEMENT)
      {
        const std::string& categoryId{category->ValueStr()};
        auto setting = category->FirstChild();
        while (setting)
        {
          parseSettingValue(setting, categoryId);

          setting = setting->NextSibling();
        }
      }
      else
        parseSettingValue(category);

      category = category->NextSibling();
    }
  }
  // for old setting values do it manually
  else if (!LoadOldSettingValues(doc, settingValues))
  {
    m_logger->error("failed to determine setting values from old format");
    return false;
  }

  // process all settings
  for (const auto& [settingId, settingValue] : settingValues)
  {
    // ignore setting values without a setting identifier
    if (settingId.empty())
      continue;

    // try to find a matching setting
    SettingPtr newSetting{GetSetting(settingId)};
    if (!newSetting)
    {
      // create a hidden/internal string setting on-the-fly
      newSetting =
          AddSettingWithoutDefinition<CSettingString>(*this, settingId, settingValue, m_logger);
    }

    // try to load the old setting value
    if (!newSetting)
    {
      m_logger->error(R"(had null newSetting for value "{}" for setting {})", settingValue,
                      settingId);
    }
    else if (!newSetting->FromString(settingValue))
    {
      m_logger->warn(R"(failed to load value "{}" for setting {})", settingValue, settingId);
    }
  }

  SetLoaded();

  return true;
}

bool CAddonSettings::Save(CXBMCTinyXML& doc) const
{
  std::unique_lock lock(m_critical);
  if (!m_initialized)
    return false;

  if (!SaveValuesToXml(doc))
  {
    m_logger->error("failed to save settings");
    return false;
  }

  return true;
}

bool CAddonSettings::HasSettings() const
{
  return IsInitialized() && GetSettingsManager()->HasSettings();
}

bool CAddonSettings::Save()
{
  std::shared_ptr<IAddon> addon = m_addon.lock();
  assert(addon);
  if (addon)
    return addon->SaveSettings();
  else
    return false;
}

std::string CAddonSettings::GetSettingLabel(int label) const
{
  if (label < UnknownSettingLabelIdStart || label >= m_unknownSettingLabelId)
    return "";

  const auto labelIt = m_unknownSettingLabels.find(label);
  if (labelIt == m_unknownSettingLabels.end())
    return "";

  return labelIt->second;
}

std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, bool value)
{
  return AddSettingWithoutDefinition<CSettingBool>(*this, settingId, value, m_logger);
}

std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, int value)
{
  return AddSettingWithoutDefinition<CSettingInt>(*this, settingId, value, m_logger);
}

std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, double value)
{
  return AddSettingWithoutDefinition<CSettingNumber>(*this, settingId, value, m_logger);
}

std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId,
                                                     const std::string& value)
{
  return AddSettingWithoutDefinition<CSettingString>(*this, settingId, value, m_logger);
}

void CAddonSettings::InitializeSettingTypes()
{
  GetSettingsManager()->RegisterSettingType("addon", this);
  GetSettingsManager()->RegisterSettingType("date", this);
  GetSettingsManager()->RegisterSettingType("path", this);
  GetSettingsManager()->RegisterSettingType("time", this);
  GetSettingsManager()->RegisterSettingType("urlencodedstring", this);
}

void CAddonSettings::InitializeControls()
{
  GetSettingsManager()->RegisterSettingControl("toggle", this);
  GetSettingsManager()->RegisterSettingControl("spinner", this);
  GetSettingsManager()->RegisterSettingControl("edit", this);
  GetSettingsManager()->RegisterSettingControl("button", this);
  GetSettingsManager()->RegisterSettingControl("list", this);
  GetSettingsManager()->RegisterSettingControl("slider", this);
  GetSettingsManager()->RegisterSettingControl("range", this);
  GetSettingsManager()->RegisterSettingControl("title", this);
  GetSettingsManager()->RegisterSettingControl("colorbutton", this);
}

void CAddonSettings::InitializeConditions()
{
  CSettingConditions::Initialize();

  // add basic conditions
  const CSettingConditions::SimpleConditions& simpleConditions =
      CSettingConditions::GetSimpleConditions();
  for (const auto& condition : simpleConditions)
    GetSettingsManager()->AddCondition(condition);

  GetSettingsManager()->AddDynamicCondition("InfoBool", InfoBool);
}

bool CAddonSettings::InitializeDefinitions(const CXBMCTinyXML& doc)
{
  // figure out the version of the setting definitions
  uint32_t version = 0;
  if (!ParseSettingVersion(doc, version))
  {
    m_logger->error("failed to determine setting definitions version");
    return false;
  }

  // for new/"normal" setting definitions use the standard process
  if (version != 0)
    return InitializeDefinitionsFromXml(doc);

  // for old setting definitions do it manually
  return InitializeFromOldSettingDefinitions(doc);
}

bool CAddonSettings::ParseSettingVersion(const CXBMCTinyXML& doc, uint32_t& version) const
{
  const TiXmlElement* root = doc.RootElement();
  if (!root)
    return false;

  if (!StringUtils::EqualsNoCase(root->ValueStr(), SETTING_XML_ROOT))
  {
    m_logger->error("error reading setting definitions: no <settings> tag");
    return false;
  }

  version = GetSettingsManager()->ParseVersion(root);
  return true;
}

std::shared_ptr<CSettingGroup> CAddonSettings::ParseOldSettingElement(
    const TiXmlElement* categoryElement,
    const std::shared_ptr<CSettingCategory>& category,
    std::set<std::string, std::less<>>& settingIds)
{
  // build a vector of settings from the same category
  std::vector<std::shared_ptr<const CSetting>> categorySettings;

  // prepare for settings with enable/visible conditions
  struct SettingWithConditions
  {
    SettingPtr setting;
    std::string enableCondition;
    std::string visibleCondition;
    SettingDependencies deps;
  };
  std::vector<SettingWithConditions> settingsWithConditions;

  auto group{std::make_shared<CSettingGroup>("0", GetSettingsManager())};
  uint32_t groupId = 1;

  // go through all settings in the category
  const TiXmlElement* settingElement = categoryElement->FirstChildElement("setting");
  while (settingElement)
  {
    // read the possible attributes
    const std::string settingType{XMLUtils::GetAttribute(settingElement, "type")};
    const std::string settingId{XMLUtils::GetAttribute(settingElement, "id")};
    const std::string defaultValue{XMLUtils::GetAttribute(settingElement, "default")};
    const std::string settingValues{XMLUtils::GetAttribute(settingElement, "values")};
    const std::vector<std::string> settingLValues{StringUtils::Split(
        XMLUtils::GetAttribute(settingElement, "lvalues"), OldSettingValuesSeparator)};
    int settingLabel = -1;
    const bool settingLabelParsed{ParseOldLabel(settingElement, settingId, settingLabel)};

    SettingPtr setting;
    if (settingType == "sep" || settingType == "lsep")
    {
      // check if we need to create a new group
      if (!group->GetSettings().empty())
      {
        // add the current group to the category
        category->AddGroup(group);

        // and create a new one
        group = std::make_shared<CSettingGroup>(std::to_string(groupId), GetSettingsManager());
        groupId += 1;
      }

      if (settingType == "lsep" && settingLabelParsed)
        group->SetLabel(settingLabel);
    }
    else if (settingId.empty() || settingType == "action")
    {
      if (settingType == "action")
        setting = InitializeFromOldSettingAction(settingId, settingElement, defaultValue);
      else
        setting = InitializeFromOldSettingLabel();
    }
    else if (settingType == "bool")
      setting = InitializeFromOldSettingBool(settingId, settingElement, defaultValue);
    else if (settingType == "text" || settingType == "ipaddress")
      setting = InitializeFromOldSettingTextIpAddress(settingId, settingType, settingElement,
                                                      defaultValue, settingLabel);
    else if (settingType == "number")
      setting =
          InitializeFromOldSettingNumber(settingId, settingElement, defaultValue, settingLabel);
    else if (settingType == "video" || settingType == "audio" || settingType == "image" ||
             settingType == "executable" || settingType == "file" || settingType == "folder")
      setting = InitializeFromOldSettingPath(settingId, settingType, settingElement, defaultValue,
                                             settingLabel);
    else if (settingType == "date")
      setting = InitializeFromOldSettingDate(settingId, settingElement, defaultValue, settingLabel);
    else if (settingType == "time")
      setting = InitializeFromOldSettingTime(settingId, settingElement, defaultValue, settingLabel);
    else if (settingType == "select")
      setting = InitializeFromOldSettingSelect(settingId, settingElement, defaultValue,
                                               settingLabel, settingValues, settingLValues);
    else if (settingType == "addon")
      setting =
          InitializeFromOldSettingAddon(settingId, settingElement, defaultValue, settingLabel);
    else if (settingType == "enum" || settingType == "labelenum")
      setting = InitializeFromOldSettingEnums(settingId, settingType, settingElement, defaultValue,
                                              settingValues, settingLValues);
    else if (settingType == "fileenum")
      setting =
          InitializeFromOldSettingFileEnum(settingId, settingElement, defaultValue, settingValues);
    else if (settingType == "rangeofnum")
      setting = InitializeFromOldSettingRangeOfNum(settingId, settingElement, defaultValue);
    else if (settingType == "slider")
      setting = InitializeFromOldSettingSlider(settingId, settingElement, defaultValue);
    else if (settingType.empty())
    {
      // setting definitions without a type are considered as "text" / strings but are hidden
      setting = InitializeFromOldSettingTextIpAddress(settingId, "text", settingElement,
                                                      defaultValue, settingLabel);
      setting->SetLevel(SettingLevel::Internal);
    }
    else
    {
      m_logger->warn(R"(failed to parse old setting definition for "{}" of type "{}")", settingId,
                     settingType);
    }

    // process general properties
    if (setting)
    {
      // set the default level to be Basic
      if (setting->GetLevel() != SettingLevel::Internal)
      {
        setting->SetLevel(SettingLevel::Basic);
      }

      // use the setting's ID if there's no label
      if (settingLabel < 0)
      {
        settingLabel = m_unknownSettingLabelId;
        m_unknownSettingLabelId += 1;

        m_unknownSettingLabels.try_emplace(settingLabel, settingId);
      }

      // set the setting's label
      setting->SetLabel(settingLabel);

      // handle subsettings
      bool isSubsetting = false;
      if (settingElement->QueryBoolAttribute("subsetting", &isSubsetting) == TIXML_SUCCESS &&
          isSubsetting)
      {
        // find the last non-subsetting in the current group and use that as the parent setting
        const SettingList& groupSettings{group->GetSettings()};
        const auto parentSetting =
            std::find_if(groupSettings.crbegin(), groupSettings.crend(),
                         [](const SettingConstPtr& s) { return s->GetParent().empty(); });

        if (parentSetting != groupSettings.crend())
        {
          if ((*parentSetting)->IsReference())
            setting->SetParent((*parentSetting)->GetReferencedId());
          else
            setting->SetParent((*parentSetting)->GetId());
        }
      }

      SettingWithConditions settingWithConditions;

      // parse enable status
      const std::string conditionEnable{XMLUtils::GetAttribute(settingElement, "enable")};
      if (StringUtils::EqualsNoCase(conditionEnable, "true"))
        setting->SetEnabled(true);
      else if (StringUtils::EqualsNoCase(conditionEnable, "false"))
        setting->SetEnabled(false);
      else if (!conditionEnable.empty())
        settingWithConditions.enableCondition = conditionEnable;

      // parse visible status
      const std::string conditionVisible{XMLUtils::GetAttribute(settingElement, "visible")};
      if (StringUtils::EqualsNoCase(conditionVisible, "true"))
        setting->SetVisible(true);
      else if (StringUtils::EqualsNoCase(conditionVisible, "false"))
        setting->SetVisible(false);
      else if (!conditionVisible.empty())
        settingWithConditions.visibleCondition = conditionVisible;

      // check if there already is a setting with the setting identifier
      if (settingIds.contains(settingId))
      {
        // turn the setting into a reference setting
        setting->MakeReference();
      }
      else
      {
        // add the setting's identifier to the list of all identifiers
        settingIds.insert(setting->GetId());
      }

      if (!settingWithConditions.enableCondition.empty() ||
          !settingWithConditions.visibleCondition.empty())
      {
        settingWithConditions.setting = setting;
        settingsWithConditions.emplace_back(settingWithConditions);
      }

      // add the setting to the list of settings from the same category
      categorySettings.emplace_back(setting);

      // add the setting to the current group
      group->AddSetting(setting);
    }
    else
    {
      // add a dummy setting for the group / separator to the list of settings from the same category
      categorySettings.emplace_back(nullptr);
    }

    // look for the next setting
    settingElement = settingElement->NextSiblingElement("setting");
  }

  // process settings with enable/visible conditions
  for (auto& setting : settingsWithConditions)
  {
    if (!setting.enableCondition.empty())
    {
      CSettingDependency dependencyEnable(SettingDependencyType::Enable, GetSettingsManager());
      if (ParseOldCondition(setting.setting, categorySettings, setting.enableCondition,
                            dependencyEnable))
        setting.deps.emplace_back(dependencyEnable);
      else
      {
        m_logger->warn(
            R"(failed to parse enable condition "{}" of old setting definition for "{}")",
            setting.enableCondition, setting.setting->GetId());
      }
    }

    if (!setting.visibleCondition.empty())
    {
      CSettingDependency dependencyVisible(SettingDependencyType::Visible, GetSettingsManager());
      if (ParseOldCondition(setting.setting, categorySettings, setting.visibleCondition,
                            dependencyVisible))
        setting.deps.emplace_back(dependencyVisible);
      else
      {
        m_logger->warn(
            R"(failed to parse visible condition "{}" of old setting definition for "{}")",
            setting.visibleCondition, setting.setting->GetId());
      }
    }

    // set dependencies
    setting.setting->SetDependencies(setting.deps);
  }

  return group;
}

std::shared_ptr<CSettingCategory> CAddonSettings::ParseOldCategoryElement(
    uint32_t& categoryId,
    const TiXmlElement* categoryElement,
    std::set<std::string, std::less<>>& settingIds)
{
  // create the category
  auto category{std::make_shared<CSettingCategory>(StringUtils::Format("category{}", categoryId),
                                                   GetSettingsManager())};
  categoryId += 1;

  // try to get the category's label and fall back to "General"
  int categoryLabel = 128;
  ParseOldLabel(categoryElement,
                CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(categoryLabel),
                categoryLabel);
  category->SetLabel(categoryLabel);

  // prepare a setting group
  const std::shared_ptr<CSettingGroup> group{
      ParseOldSettingElement(categoryElement, category, settingIds)};

  // add the group to the category
  category->AddGroup(group);

  return category;
}

bool CAddonSettings::InitializeFromOldSettingDefinitions(const CXBMCTinyXML& doc)
{
  m_logger->debug("trying to load setting definitions from old format...");

  const TiXmlElement* root = doc.RootElement();
  if (!root)
    return false;

  const auto section{std::make_shared<CSettingSection>(m_addonId, GetSettingsManager())};

  std::shared_ptr<CSettingCategory> category;
  uint32_t categoryId = 0;

  // Settings id set
  std::set<std::string, std::less<>> settingIds;

  // Special case for no category settings
  section->AddCategory(ParseOldCategoryElement(categoryId, root, settingIds));

  const TiXmlElement* categoryElement = root->FirstChildElement("category");
  while (categoryElement)
  {
    section->AddCategory(ParseOldCategoryElement(categoryId, categoryElement, settingIds));

    // look for the next category
    categoryElement = categoryElement->NextSiblingElement("category");
  }

  // add the section to the settingsmanager
  GetSettingsManager()->AddSection(section);

  return true;
}

SettingPtr CAddonSettings::InitializeFromOldSettingAction(const std::string& settingId,
                                                          const TiXmlElement* settingElement,
                                                          const std::string& defaultValue)
{
  // parse the action attribute
  std::string action = XMLUtils::GetAttribute(settingElement, "action");
  // replace $CWD with the url of the add-on
  StringUtils::Replace(action, "$CWD", m_addonPath);
  // replace $ID with the id of the add-on
  StringUtils::Replace(action, "$ID", m_addonId);

  // prepare the setting's control
  const auto control{std::make_shared<CSettingControlButton>()};
  control->SetFormat("action");

  SettingPtr setting;
  // action settings don't require a setting id
  if (settingId.empty())
  {
    const std::string actionSettingId{StringUtils::Format("action{}", m_unidentifiedSettingId)};
    m_unidentifiedSettingId += 1;

    const auto settingAction{
        std::make_shared<CSettingAction>(actionSettingId, GetSettingsManager())};
    settingAction->SetData(action);

    setting = settingAction;
  }
  else
  {
    // assume that the setting might store a value as a string
    const auto settingString{std::make_shared<CSettingString>(settingId, GetSettingsManager())};
    settingString->SetDefault(defaultValue);
    settingString->SetAllowEmpty(true);

    control->SetActionData(action);

    setting = settingString;
  }

  // get any options
  const std::string option{XMLUtils::GetAttribute(settingElement, "option")};
  // handle the "close" option
  if (StringUtils::EqualsNoCase(option, "close"))
    control->SetCloseDialog(true);

  setting->SetControl(control);

  return setting;
}

std::shared_ptr<CSetting> CAddonSettings::InitializeFromOldSettingLabel()
{
  // label settings don't require a setting id
  const std::string labelSettingId{StringUtils::Format("label{}", m_unidentifiedSettingId)};
  m_unidentifiedSettingId += 1;

  const auto settingLabel{std::make_shared<CSettingString>(labelSettingId, GetSettingsManager())};

  // create the setting's control
  settingLabel->SetControl(std::make_shared<CSettingControlLabel>());

  return settingLabel;
}

SettingPtr CAddonSettings::InitializeFromOldSettingBool(const std::string& settingId,
                                                        const TiXmlElement* /*settingElement*/,
                                                        const std::string& defaultValue)
{
  auto setting{std::make_shared<CSettingBool>(settingId, GetSettingsManager())};
  if (setting->FromString(defaultValue))
    setting->SetDefault(setting->GetValue());

  setting->SetControl(std::make_shared<CSettingControlCheckmark>());

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingTextIpAddress(const std::string& settingId,
                                                                 const std::string& settingType,
                                                                 const TiXmlElement* settingElement,
                                                                 const std::string& defaultValue,
                                                                 const int settingLabel)
{
  std::shared_ptr<CSettingString> setting;
  const auto control{std::make_shared<CSettingControlEdit>()};
  control->SetHeading(settingLabel);

  if (settingType == "ipaddress")
  {
    setting = std::make_shared<CSettingString>(settingId, GetSettingsManager());
    control->SetFormat("ip");
  }
  else if (settingType == "text")
  {
    const std::string option{XMLUtils::GetAttribute(settingElement, "option")};

    if (StringUtils::EqualsNoCase(option, "urlencoded"))
    {
      setting = std::make_shared<CSettingUrlEncodedString>(settingId, GetSettingsManager());
      control->SetFormat("urlencoded");
    }
    else
    {
      setting = std::make_shared<CSettingString>(settingId, GetSettingsManager());
      control->SetFormat("string");
      control->SetHidden(StringUtils::EqualsNoCase(option, "hidden"));
    }
  }

  setting->SetDefault(defaultValue);
  setting->SetAllowEmpty(true);
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingNumber(const std::string& settingId,
                                                          const TiXmlElement* /*settingElement*/,
                                                          const std::string& defaultValue,
                                                          const int settingLabel)
{
  auto setting{std::make_shared<CSettingInt>(settingId, GetSettingsManager())};
  if (setting->FromString(defaultValue))
    setting->SetDefault(setting->GetValue());

  const auto control{std::make_shared<CSettingControlEdit>()};
  control->SetHeading(settingLabel);
  control->SetFormat("integer");
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingPath(const std::string& settingId,
                                                        const std::string& settingType,
                                                        const TiXmlElement* settingElement,
                                                        const std::string& defaultValue,
                                                        const int settingLabel)
{
  auto setting{std::make_shared<CSettingPath>(settingId, GetSettingsManager())};
  setting->SetDefault(defaultValue);

  // parse sources/shares
  const std::string source{XMLUtils::GetAttribute(settingElement, "source")};
  if (!source.empty())
    setting->SetSources({source});

  // setup masking
  const std::string audioMask{CServiceBroker::GetFileExtensionProvider().GetMusicExtensions()};
  const std::string videoMask{CServiceBroker::GetFileExtensionProvider().GetVideoExtensions()};
  const std::string imageMask{CServiceBroker::GetFileExtensionProvider().GetPictureExtensions()};
  std::string execMask = "";
#if defined(TARGET_WINDOWS)
  execMask = ".exe|.bat|.cmd|.py";
#endif // defined(TARGET_WINDOWS)

  std::string mask = XMLUtils::GetAttribute(settingElement, "mask");
  if (!mask.empty())
  {
    // convert mask qualifiers
    StringUtils::Replace(mask, "$AUDIO", audioMask);
    StringUtils::Replace(mask, "$VIDEO", videoMask);
    StringUtils::Replace(mask, "$IMAGE", imageMask);
    StringUtils::Replace(mask, "$EXECUTABLE", execMask);
  }
  else
  {
    if (settingType == "video")
      mask = videoMask;
    else if (settingType == "audio")
      mask = audioMask;
    else if (settingType == "image")
      mask = imageMask;
    else if (settingType == "executable")
      mask = execMask;
  }
  setting->SetMasking(mask);

  // parse options
  const std::string option{XMLUtils::GetAttribute(settingElement, "option")};
  setting->SetWritable(StringUtils::EqualsNoCase(option, "writeable"));

  const auto control{std::make_shared<CSettingControlButton>()};
  if (settingType == "folder")
    control->SetFormat("path");
  else if (settingType == "image")
    control->SetFormat("image");
  else
  {
    control->SetFormat("file");

    // parse the options
    const std::vector<std::string> options{StringUtils::Split(option, OldSettingValuesSeparator)};
    control->SetUseImageThumbs(std::ranges::find(options, "usethumbs") != options.cend());
    control->SetUseFileDirectories(std::ranges::find(options, "treatasfolder") != options.cend());
  }
  control->SetHeading(settingLabel);
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingDate(const std::string& settingId,
                                                        const TiXmlElement* /*settingElement*/,
                                                        const std::string& defaultValue,
                                                        const int settingLabel)
{
  auto setting{std::make_shared<CSettingDate>(settingId, GetSettingsManager())};
  if (setting->FromString(defaultValue))
    setting->SetDefault(setting->GetValue());

  const auto control{std::make_shared<CSettingControlButton>()};
  control->SetFormat("date");
  control->SetHeading(settingLabel);
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingTime(const std::string& settingId,
                                                        const TiXmlElement* /*settingElement*/,
                                                        const std::string& defaultValue,
                                                        const int settingLabel)
{
  auto setting{std::make_shared<CSettingTime>(settingId, GetSettingsManager())};
  if (setting->FromString(defaultValue))
    setting->SetDefault(setting->GetValue());

  const auto control{std::make_shared<CSettingControlButton>()};
  control->SetFormat("time");
  control->SetHeading(settingLabel);
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingSelect(
    const std::string& settingId,
    const TiXmlElement* settingElement,
    const std::string& defaultValue,
    const int settingLabel,
    const std::string& settingValues,
    const std::vector<std::string>& settingLValues)
{
  // process values and lvalues
  std::vector<std::string> values;
  if (!settingLValues.empty())
    values = settingLValues;
  else
    values = StringUtils::Split(settingValues, OldSettingValuesSeparator);

  SettingPtr setting;
  if (!values.empty())
  {
    if (settingLValues.empty())
    {
      const auto settingString{std::make_shared<CSettingString>(settingId, GetSettingsManager())};
      settingString->SetDefault(defaultValue);

      StringSettingOptions options;
      for (const auto& value : values)
        options.emplace_back(value, value);
      settingString->SetOptions(options);

      setting = settingString;
    }
    else
    {
      const auto settingInt{std::make_shared<CSettingInt>(settingId, GetSettingsManager())};
      if (settingInt->FromString(defaultValue))
        settingInt->SetDefault(settingInt->GetValue());

      TranslatableIntegerSettingOptions options;
      for (uint32_t i = 0; i < values.size(); ++i)
        options.emplace_back(static_cast<int>(std::strtol(values[i].c_str(), nullptr, 0)), i);
      settingInt->SetTranslatableOptions(options);

      setting = settingInt;
    }
  }
  else
  {
    // parse sources/shares
    const std::string source{XMLUtils::GetAttribute(settingElement, "source")};
    if (!source.empty())
      setting = InitializeFromOldSettingFileWithSource(settingId, settingElement, defaultValue,
                                                       settingValues);
    else
      m_logger->warn(R"(failed to parse old setting definition for "{}" of type "select")",
                     settingId);
  }

  if (setting)
  {
    const auto control{std::make_shared<CSettingControlList>()};
    control->SetHeading(settingLabel);
    control->SetFormat("string");
    setting->SetControl(control);
  }

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingAddon(const std::string& settingId,
                                                         const TiXmlElement* settingElement,
                                                         const std::string& defaultValue,
                                                         const int settingLabel)
{
  // get addon types
  std::string addonTypeStr = XMLUtils::GetAttribute(settingElement, "addontype");
  const std::vector<std::string> addonTypesStr{StringUtils::Split(addonTypeStr, ",")};
  std::set<AddonType> addonTypes;
  for (auto addonType : addonTypesStr)
  {
    const AddonType type{ADDON::CAddonInfo::TranslateType(StringUtils::Trim(addonType))};
    if (type != ADDON::AddonType::UNKNOWN)
      addonTypes.insert(type);
  }

  if (addonTypes.empty())
  {
    m_logger->error(R"(missing addon type for addon setting "{}")", settingId);
    return nullptr;
  }

  // TODO: support multiple addon types
  if (addonTypes.size() > 1)
  {
    m_logger->error(R"(multiple addon types are not supported (addon setting "{}"))", settingId);
    return nullptr;
  }

  // parse addon ids
  std::vector<std::string> addonIds{StringUtils::Split(defaultValue, ",")};

  // parse multiselect option
  bool multiselect = false;
  settingElement->QueryBoolAttribute("multiselect", &multiselect);

  // sanity check
  if (addonIds.size() > 1 && !multiselect)
  {
    m_logger->warn(R"(multiple default addon ids on non-multiselect addon setting "{}")",
                   settingId);
    addonIds.erase(++addonIds.begin(), addonIds.end());
  }

  const auto settingAddon{std::make_shared<CSettingAddon>(settingId, GetSettingsManager())};
  settingAddon->SetAddonType(*addonTypes.begin());

  SettingPtr setting = settingAddon;
  if (multiselect)
  {
    const auto settingList{
        std::make_shared<CSettingList>(settingId, settingAddon, GetSettingsManager())};
    settingList->SetDelimiter(",");
    if (settingList->FromString(addonIds))
      settingList->SetDefault(settingList->GetValue());

    setting = settingList;
  }
  else if (!addonIds.empty())
    settingAddon->SetDefault(addonIds.front());

  const auto control{std::make_shared<CSettingControlButton>()};
  control->SetFormat("addon");
  control->SetHeading(settingLabel);
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingEnums(
    const std::string& settingId,
    const std::string& settingType,
    const TiXmlElement* settingElement,
    const std::string& defaultValue,
    const std::string& settingValues,
    const std::vector<std::string>& settingLValues)
{
  // process values and lvalues
  std::vector<std::string> values;
  if (!settingLValues.empty())
    values = settingLValues;
  else if (settingValues == "$HOURS")
  {
    for (uint32_t hour = 0; hour < 24; hour++)
      values.emplace_back(
          CDateTime(2000, 1, 1, hour, 0, 0).GetAsLocalizedTime(g_langInfo.GetTimeFormat(), false));
  }
  else
    values = StringUtils::Split(settingValues, OldSettingValuesSeparator);

  // process entries
  const std::vector<std::string> settingEntries{StringUtils::Split(
      XMLUtils::GetAttribute(settingElement, "entries"), OldSettingValuesSeparator)};

  // process sort
  bool sortAscending = false;
  std::string sort = XMLUtils::GetAttribute(settingElement, "sort");
  if (sort == "true" || sort == "yes")
    sortAscending = true;

  SettingPtr setting;
  if (settingType == "enum")
  {
    const auto settingInt{std::make_shared<CSettingInt>(settingId, GetSettingsManager())};

    if (settingLValues.empty())
    {
      IntegerSettingOptions options;
      for (uint32_t i = 0; i < values.size(); ++i)
      {
        const std::string& label{values[i]};
        int value = i;
        if (settingEntries.size() > i)
          value = static_cast<int>(std::strtol(settingEntries[i].c_str(), nullptr, 0));

        options.emplace_back(label, value);
      }

      settingInt->SetOptions(options);
    }
    else
    {
      TranslatableIntegerSettingOptions options;
      for (uint32_t i = 0; i < values.size(); ++i)
      {
        const auto label{static_cast<int>(std::strtol(values[i].c_str(), nullptr, 0))};
        int value = i;
        if (settingEntries.size() > i)
          value = static_cast<int>(std::strtol(settingEntries[i].c_str(), nullptr, 0));

        options.emplace_back(label, value);
      }

      settingInt->SetTranslatableOptions(options);
    }

    if (sortAscending)
      settingInt->SetOptionsSort(SettingOptionsSort::Ascending);

    // set the default value
    if (settingInt->FromString(defaultValue))
      settingInt->SetDefault(settingInt->GetValue());

    setting = settingInt;
  }
  else
  {
    const auto settingString{std::make_shared<CSettingString>(settingId, GetSettingsManager())};

    if (settingLValues.empty())
    {
      StringSettingOptions options;
      for (uint32_t i = 0; i < values.size(); ++i)
      {
        std::string value = values[i];
        if (settingEntries.size() > i)
          value = settingEntries[i];

        options.emplace_back(value, value);
      }

      settingString->SetOptions(options);
    }
    else
    {
      TranslatableStringSettingOptions options;
      for (uint32_t i = 0; i < values.size(); ++i)
      {
        const int label{static_cast<int>(std::strtol(values[i].c_str(), nullptr, 0))};
        std::string value{
            CServiceBroker::GetResourcesComponent().GetLocalizeStrings().GetAddonString(m_addonId,
                                                                                        label)};
        if (settingEntries.size() > i)
          value = settingEntries[i];

        options.emplace_back(label, value);
      }

      settingString->SetTranslatableOptions(options);
    }

    if (sortAscending)
      settingString->SetOptionsSort(SettingOptionsSort::Ascending);

    // set the default value
    settingString->SetDefault(defaultValue);

    setting = settingString;
  }

  const auto control{std::make_shared<CSettingControlSpinner>()};
  control->SetFormat("string");
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingFileEnum(const std::string& settingId,
                                                            const TiXmlElement* settingElement,
                                                            const std::string& defaultValue,
                                                            const std::string& settingValues)
{
  SettingPtr setting{InitializeFromOldSettingFileWithSource(settingId, settingElement, defaultValue,
                                                            settingValues)};

  const auto control{std::make_shared<CSettingControlSpinner>()};
  control->SetFormat("string");
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingRangeOfNum(const std::string& settingId,
                                                              const TiXmlElement* settingElement,
                                                              const std::string& defaultValue)
{
  auto setting{std::make_shared<CSettingNumber>(settingId, GetSettingsManager())};
  if (setting->FromString(defaultValue))
    setting->SetDefault(setting->GetValue());

  // parse rangestart and rangeend
  double rangeStart{0.0};
  double rangeEnd{1.0};
  settingElement->QueryDoubleAttribute("rangestart", &rangeStart);
  settingElement->QueryDoubleAttribute("rangeend", &rangeEnd);
  setting->SetMinimum(rangeStart);
  setting->SetMaximum(rangeEnd);

  // parse elements
  uint32_t elements = 2;
  settingElement->QueryUnsignedAttribute("elements", &elements);
  if (elements > 1)
    setting->SetStep((rangeEnd - rangeStart) / (elements - 1));

  // parse valueformat
  int valueFormat = -1;
  settingElement->QueryIntAttribute("valueformat", &valueFormat);

  const auto control{std::make_shared<CSettingControlSpinner>()};
  control->SetFormat("string");
  control->SetFormatLabel(valueFormat);
  setting->SetControl(control);

  return setting;
}

SettingPtr CAddonSettings::InitializeFromOldSettingSlider(const std::string& settingId,
                                                          const TiXmlElement* settingElement,
                                                          const std::string& defaultValue)
{
  // parse range
  double min{0.0};
  double max{100.0};
  double step{1.0};
  const std::vector<std::string> range{
      StringUtils::Split(XMLUtils::GetAttribute(settingElement, "range"), ',')};

  if (range.size() > 1)
  {
    min = std::strtod(range[0].c_str(), nullptr);

    if (range.size() > 2)
    {
      max = std::strtod(range[2].c_str(), nullptr);
      step = std::strtod(range[1].c_str(), nullptr);
    }
    else
      max = std::strtod(range[1].c_str(), nullptr);
  }

  // parse option
  const std::string option{XMLUtils::GetAttribute(settingElement, "option")};
  if (option.empty() || StringUtils::EqualsNoCase(option, "float"))
  {
    auto setting{std::make_shared<CSettingNumber>(settingId, GetSettingsManager())};
    if (setting->FromString(defaultValue))
      setting->SetDefault(setting->GetValue());

    setting->SetMinimum(min);
    setting->SetStep(step);
    setting->SetMaximum(max);

    const auto control{std::make_shared<CSettingControlSlider>()};
    control->SetFormat("number");
    control->SetPopup(false);
    setting->SetControl(control);

    return setting;
  }

  if (StringUtils::EqualsNoCase(option, "int") || StringUtils::EqualsNoCase(option, "percent"))
  {
    auto setting{std::make_shared<CSettingInt>(settingId, GetSettingsManager())};
    if (setting->FromString(defaultValue))
      setting->SetDefault(setting->GetValue());

    setting->SetMinimum(static_cast<int>(min));
    setting->SetStep(static_cast<int>(step));
    setting->SetMaximum(static_cast<int>(max));

    const auto control{std::make_shared<CSettingControlSlider>()};
    control->SetFormat(StringUtils::EqualsNoCase(option, "int") ? "integer" : "percentage");
    control->SetPopup(false);
    setting->SetControl(control);

    return setting;
  }

  m_logger->warn(
      R"(ignoring old setting definition for "{}" of type "slider" because of unknown option "{}")",
      settingId, option);

  return nullptr;
}

SettingPtr CAddonSettings::InitializeFromOldSettingFileWithSource(
    const std::string& settingId,
    const TiXmlElement* settingElement,
    const std::string& defaultValue,
    std::string source)
{
  auto setting{std::make_shared<CSettingPath>(settingId, GetSettingsManager())};
  setting->SetDefault(defaultValue);

  if (source.find("$PROFILE") != std::string::npos)
    StringUtils::Replace(source, "$PROFILE", m_addonProfile);
  else
    source = URIUtils::AddFileToFolder(m_addonPath, source);

  setting->SetSources({source});

  // process the path/file mask
  setting->SetMasking(XMLUtils::GetAttribute(settingElement, "mask"));

  // process option
  const std::string option{XMLUtils::GetAttribute(settingElement, "option")};
  setting->SetHideExtension(StringUtils::EqualsNoCase(option, "hideext"));

  setting->SetOptionsFiller(FileEnumSettingOptionsFiller);

  return setting;
}

bool CAddonSettings::LoadOldSettingValues(
    const CXBMCTinyXML& doc, std::map<std::string, std::string, std::less<>>& settings) const
{
  if (!doc.RootElement())
    return false;

  const TiXmlElement* category = doc.RootElement()->FirstChildElement("category");
  if (!category)
    category = doc.RootElement();

  while (category)
  {
    const TiXmlElement* setting = category->FirstChildElement("setting");
    while (setting)
    {
      const char* id = setting->Attribute("id");
      const char* value = setting->Attribute("value");
      if (id && value)
        settings[id] = value;

      setting = setting->NextSiblingElement("setting");
    }

    category = category->NextSiblingElement("category");
  }

  return !settings.empty();
}

bool CAddonSettings::ParseOldLabel(const TiXmlElement* element,
                                   const std::string& /*settingId*/,
                                   int& labelId)
{
  labelId = -1;
  if (!element)
    return false;

  // label value as a string
  std::string labelString;
  element->QueryStringAttribute("label", &labelString);

  const bool parsed{!labelString.empty()};

  // try to parse the label as a pure number, i.e. a localized string
  if (parsed)
  {
    char* endptr{nullptr};
    labelId = static_cast<int>(std::strtol(labelString.c_str(), &endptr, 10));
    if (!endptr || *endptr == '\0')
      return true;
  }
  // make sure the label string is not empty
  else
    labelString = " ";

  labelId = m_unknownSettingLabelId;
  m_unknownSettingLabelId += 1;
  m_unknownSettingLabels.try_emplace(labelId, labelString);

  return parsed;
}

bool CAddonSettings::ParseOldCondition(const std::shared_ptr<const CSetting>& setting,
                                       const std::vector<std::shared_ptr<const CSetting>>& settings,
                                       const std::string& condition,
                                       CSettingDependency& dependeny) const
{
  if (!setting)
    return false;

  if (condition.empty())
    return true;

  // find the index of the setting in the list of all settings of the category
  const auto settingIt = std::ranges::find_if(settings,
                                              [setting](const SettingConstPtr& otherSetting)
                                              {
                                                if (!otherSetting)
                                                  return false;

                                                return setting->GetId() == otherSetting->GetId();
                                              });
  if (settingIt == settings.cend())
  {
    m_logger->warn(R"(failed to parse old setting conditions "{}" for "{}")", condition,
                   setting->GetId());
    return false;
  }
  const auto currentSettingIndex{std::distance(settings.cbegin(), settingIt)};

  CSettingDependencyConditionCombinationPtr dependencyCombination;
  std::vector<std::string> conditions;
  if (condition.find('+') != std::string::npos)
  {
    StringUtils::Tokenize(condition, conditions, '+');
    dependencyCombination = dependeny.And();
  }
  else
  {
    StringUtils::Tokenize(condition, conditions, '|');
    dependencyCombination = dependeny.Or();
  }

  bool error = false;
  for (const auto& cond : conditions)
  {
    ConditionExpression expression;
    if (!ParseOldConditionExpression(cond, expression))
      continue;

    // determine the absolute setting index
    const auto absoluteSettingIndex{currentSettingIndex + expression.m_relativeSettingIndex};

    // we cannot handle relative indices pointing to settings not belonging to the same category
    if (absoluteSettingIndex < 0 || static_cast<size_t>(absoluteSettingIndex) >= settings.size())
    {
      m_logger->warn(
          R"(cannot reference setting (relative index: {}; absolute index: {}) in another category in old setting condition "{}" for "{}")",
          expression.m_relativeSettingIndex, absoluteSettingIndex, cond, setting->GetId());
      error = true;
      continue;
    }

    const SettingConstPtr& referencedSetting = settings.at(absoluteSettingIndex);
    if (!referencedSetting)
    {
      m_logger->warn(R"(cannot reference separator setting in old setting condition "{}" for "{}")",
                     cond, setting->GetId());
      error = true;
      continue;
    }

    // try to handle some odd cases where the setting is of type string but the comparison value references the index of the value in the list of options
    if (referencedSetting->GetType() == SettingType::String &&
        StringUtils::IsNaturalNumber(expression.m_value))
    {
      // try to parse the comparison value
      const size_t valueIndex{std::strtoul(expression.m_value.c_str(), nullptr, 10)};

      const auto referencedSettingString =
          std::static_pointer_cast<const CSettingString>(referencedSetting);
      switch (referencedSettingString->GetOptionsType())
      {
        case SettingOptionsType::Static:
        {
          const StringSettingOptions& options{referencedSettingString->GetOptions()};
          if (options.size() > valueIndex)
            expression.m_value = options.at(valueIndex).value;
          break;
        }

        case SettingOptionsType::StaticTranslatable:
        {
          const TranslatableStringSettingOptions& options{
              referencedSettingString->GetTranslatableOptions()};
          if (options.size() > valueIndex)
            expression.m_value = options.at(valueIndex).second;
          break;
        }

        default:
          break;
      }
    }

    // add the condition to the value of the referenced setting
    dependencyCombination->Add(std::make_shared<CSettingDependencyCondition>(
        referencedSetting->GetId(), expression.m_value, expression.m_operator, expression.m_negated,
        GetSettingsManager()));
  }

  // if the condition doesn't depend on other settings it might be an infobool expression
  if (!error && dependencyCombination->GetOperations().empty() &&
      dependencyCombination->GetValues().empty())
    dependencyCombination->Add(std::make_shared<CSettingDependencyCondition>(
        "InfoBool", condition, "", false, GetSettingsManager()));

  return !error;
}

bool CAddonSettings::ParseOldConditionExpression(std::string str, ConditionExpression& expression)
{
  StringUtils::Trim(str);

  const size_t posOpen{str.find('(')};
  const size_t posSep{str.find(',', posOpen)};
  const size_t posClose{str.find(')', posSep)};

  if (posOpen == std::string::npos || posSep == std::string::npos || posClose == std::string::npos)
    return false;

  std::string op{str.substr(0, posOpen)};

  // check if the operator is negated
  expression.m_negated = StringUtils::StartsWith(op, "!");
  if (expression.m_negated)
    op = op.substr(1);

  // parse the operator
  if (StringUtils::EqualsNoCase(op, "eq"))
    expression.m_operator = SettingDependencyOperator::Equals;
  else if (StringUtils::EqualsNoCase(op, "gt"))
    expression.m_operator = SettingDependencyOperator::GreaterThan;
  else if (StringUtils::EqualsNoCase(op, "lt"))
    expression.m_operator = SettingDependencyOperator::LessThan;
  else
    return false;

  expression.m_relativeSettingIndex = static_cast<int32_t>(
      strtol(str.substr(posOpen + 1, posSep - posOpen - 1).c_str(), nullptr, 10));
  expression.m_value = str.substr(posSep + 1, posClose - posSep - 1);

  return true;
}

void CAddonSettings::FileEnumSettingOptionsFiller(const std::shared_ptr<const CSetting>& setting,
                                                  std::vector<StringSettingOption>& list,
                                                  std::string& /*current*/)
{
  if (!setting)
    return;

  const auto settingPath{std::dynamic_pointer_cast<const CSettingPath>(setting)};
  if (!settingPath)
    return;

  if (settingPath->GetSources().empty())
    return;

  const std::string& masking = settingPath->GetMasking(CServiceBroker::GetFileExtensionProvider());

  // fetch the matching files/directories
  CFileItemList items;
  XFILE::CDirectory::GetDirectory(settingPath->GetSources().front(), items, masking,
                                  XFILE::DIR_FLAG_NO_FILE_DIRS);

  // process the matching files/directories
  for (const auto& item : items)
  {
    if ((masking == "/" && item->IsFolder()) || !item->IsFolder())
    {
      if (settingPath->HideExtension())
        item->RemoveExtension();
      list.emplace_back(item->GetLabel(), item->GetLabel());
    }
  }
}

} // namespace ADDON
