#include "shell/settings/settings_window.h"

#include "config/config_service.h"
#include "core/deferred_call.h"
#include "core/log.h"
#include "core/ui_phase.h"
#include "i18n/i18n.h"
#include "render/render_context.h"
#include "shell/settings/settings_content.h"
#include "shell/settings/settings_entity_editor.h"
#include "shell/settings/settings_registry.h"
#include "shell/settings/settings_sidebar.h"
#include "ui/controls/box.h"
#include "ui/controls/button.h"
#include "ui/controls/flex.h"
#include "ui/controls/input.h"
#include "ui/controls/label.h"
#include "ui/controls/scroll_view.h"
#include "ui/controls/select.h"
#include "ui/controls/toggle.h"
#include "ui/palette.h"
#include "ui/style.h"
#include "wayland/wayland_connection.h"

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace {

  constexpr Logger kLog("settings");

  std::unique_ptr<Label> makeLabel(std::string_view text, float fontSize, const ThemeColor& color, bool bold = false) {
    auto label = std::make_unique<Label>();
    label->setText(text);
    label->setFontSize(fontSize);
    label->setColor(color);
    label->setBold(bold);
    return label;
  }

  std::vector<std::string> sectionKeys(const std::vector<settings::SettingEntry>& entries) {
    std::vector<std::string> sections;
    for (const auto& entry : entries) {
      if (entry.section == "bar") {
        continue;
      }
      if (std::find(sections.begin(), sections.end(), entry.section) == sections.end()) {
        sections.push_back(entry.section);
      }
    }
    return sections;
  }

  bool containsPath(const std::vector<std::vector<std::string>>& paths, const std::vector<std::string>& path) {
    return std::find(paths.begin(), paths.end(), path) != paths.end();
  }

  bool settingEntryBelongsToPage(const settings::SettingEntry& entry, std::string_view selectedSection,
                                 std::string_view selectedBarName, std::string_view selectedMonitorOverride) {
    if (selectedSection != "bar") {
      return entry.section == selectedSection;
    }

    if (entry.section != "bar" || entry.path.size() < 2 || entry.path[0] != "bar" || entry.path[1] != selectedBarName) {
      return false;
    }

    const bool entryIsMonitorOverride = entry.path.size() >= 5 && entry.path[2] == "monitor";
    if (selectedMonitorOverride.empty()) {
      return !entryIsMonitorOverride;
    }
    return entryIsMonitorOverride && entry.path[3] == selectedMonitorOverride;
  }

  std::string pageScopeKey(std::string_view selectedSection, std::string_view selectedBarName,
                           std::string_view selectedMonitorOverride) {
    if (selectedSection != "bar") {
      return std::string(selectedSection);
    }
    std::string key = "bar:" + std::string(selectedBarName);
    if (!selectedMonitorOverride.empty()) {
      key += ":monitor:" + std::string(selectedMonitorOverride);
    }
    return key;
  }

} // namespace

void SettingsWindow::initialize(WaylandConnection& wayland, ConfigService* config, RenderContext* renderContext) {
  m_wayland = &wayland;
  m_config = config;
  m_renderContext = renderContext;
  m_showAdvanced = m_config != nullptr ? m_config->config().shell.settingsShowAdvanced : false;
}

float SettingsWindow::uiScale() const {
  if (m_config == nullptr) {
    return 1.0f;
  }
  return std::max(0.1f, m_config->config().shell.uiScale);
}

void SettingsWindow::open() {
  if (m_wayland == nullptr || m_renderContext == nullptr || !m_wayland->hasXdgShell()) {
    return;
  }
  if (isOpen()) {
    m_wayland->activateSurface(m_surface->wlSurface());
    return;
  }

  m_showAdvanced = m_config != nullptr ? m_config->config().shell.settingsShowAdvanced : false;

  wl_output* output = m_wayland->lastPointerOutput();
  if (output == nullptr) {
    const auto& outs = m_wayland->outputs();
    if (!outs.empty() && outs.front().output != nullptr) {
      output = outs.front().output;
    }
  }

  m_surface = std::make_unique<ToplevelSurface>(*m_wayland);
  m_surface->setRenderContext(m_renderContext);
  m_surface->setAnimationManager(&m_animations);

  m_surface->setClosedCallback([this]() { destroyWindow(); });

  m_surface->setConfigureCallback([this](std::uint32_t /*width*/, std::uint32_t /*height*/) {
    if (m_surface != nullptr) {
      m_surface->requestLayout();
    }
  });

  m_surface->setPrepareFrameCallback(
      [this](bool needsUpdate, bool needsLayout) { prepareFrame(needsUpdate, needsLayout); });

  m_surface->setUpdateCallback([]() {});

  const float scale = uiScale();
  const std::uint32_t w = static_cast<std::uint32_t>(std::round(900.0f * scale));
  const std::uint32_t h = static_cast<std::uint32_t>(std::round(600.0f * scale));
  const std::uint32_t minW = static_cast<std::uint32_t>(std::round(800.0f * scale));
  const std::uint32_t minH = static_cast<std::uint32_t>(std::round(500.0f * scale));

  ToplevelSurfaceConfig cfg{
      .width = std::max<std::uint32_t>(1, w),
      .height = std::max<std::uint32_t>(1, h),
      .minWidth = minW,
      .minHeight = minH,
      .title = "Noctalia Settings",
      .appId = "dev.noctalia.Noctalia.Settings",
  };

  if (!m_surface->initialize(output, cfg)) {
    kLog.warn("settings: failed to create toplevel surface");
    m_surface.reset();
    return;
  }
  m_pointerInside = false;
  m_lastSceneWidth = 0;
  m_lastSceneHeight = 0;
}

void SettingsWindow::close() {
  if (!isOpen()) {
    return;
  }
  destroyWindow();
}

void SettingsWindow::destroyWindow() {
  if (m_surface != nullptr) {
    m_inputDispatcher.setSceneRoot(nullptr);
    m_surface->setSceneRoot(nullptr);
  }
  m_mainContainer = nullptr;
  m_sceneRoot.reset();
  m_surface.reset();
  m_pointerInside = false;
  m_lastSceneWidth = 0;
  m_lastSceneHeight = 0;
  m_rebuildRequested = false;
  m_focusSearchOnRebuild = false;
  m_statusMessage.clear();
  m_statusIsError = false;
  m_creatingBarName.clear();
  m_renamingBarName.clear();
  m_pendingDeleteBarName.clear();
  m_creatingMonitorOverrideBarName.clear();
  m_creatingMonitorOverrideMatch.clear();
  m_renamingMonitorOverrideBarName.clear();
  m_renamingMonitorOverrideMatch.clear();
  m_pendingDeleteMonitorOverrideBarName.clear();
  m_pendingDeleteMonitorOverrideMatch.clear();
  m_pendingResetPageScope.clear();
}

void SettingsWindow::prepareFrame(bool /*needsUpdate*/, bool needsLayout) {
  if (m_renderContext == nullptr || m_surface == nullptr) {
    return;
  }

  const auto width = m_surface->width();
  const auto height = m_surface->height();
  if (width == 0 || height == 0) {
    return;
  }

  m_renderContext->makeCurrent(m_surface->renderTarget());
  m_renderContext->syncContentScale(m_surface->renderTarget());

  // Rebuild the entire scene only on first build or when something explicitly
  // requested it (config change, nav click, etc.). Pure size changes — which
  // niri delivers at refresh rate during window animations (slide-in on focus
  // return, workspace transitions) — should just re-layout the existing tree.
  // Rebuilding on every configure causes a 25+ rebuild storm during niri
  // animations, freezing input response for ~150 ms.
  const bool firstBuild = m_sceneRoot == nullptr;
  const bool sizeChanged = !firstBuild && (m_lastSceneWidth != width || m_lastSceneHeight != height);
  const bool needRebuild = firstBuild || m_rebuildRequested;

  if (needRebuild) {
    UiPhaseScope layoutPhase(UiPhase::Layout);
    buildScene(width, height);
    m_lastSceneWidth = width;
    m_lastSceneHeight = height;
    m_rebuildRequested = false;
  } else if ((sizeChanged || needsLayout) && m_sceneRoot != nullptr) {
    UiPhaseScope layoutPhase(UiPhase::Layout);
    const float w = static_cast<float>(width);
    const float h = static_cast<float>(height);
    m_sceneRoot->setSize(w, h);
    if (m_panelBackground != nullptr) {
      m_panelBackground->setSize(w, h);
    }
    if (m_mainContainer != nullptr) {
      m_mainContainer->setSize(w, h);
    }
    m_sceneRoot->layout(*m_renderContext);
    m_lastSceneWidth = width;
    m_lastSceneHeight = height;
  }
}

void SettingsWindow::buildScene(std::uint32_t width, std::uint32_t height) {
  uiAssertNotRendering("SettingsWindow::buildScene");
  if (m_renderContext == nullptr || m_surface == nullptr) {
    return;
  }

  const float w = static_cast<float>(width);
  const float h = static_cast<float>(height);
  const float scale = uiScale();
  const Config cfg = m_config != nullptr ? m_config->config() : Config{};
  const auto availableBars = settings::barNames(cfg);
  if (availableBars.empty()) {
    m_selectedBarName.clear();
  } else if (settings::findBar(cfg, m_selectedBarName) == nullptr) {
    m_selectedBarName = availableBars.front();
  }
  const BarConfig* selectedBar = settings::findBar(cfg, m_selectedBarName);
  const BarMonitorOverride* selectedMonitorOverride = nullptr;
  if (selectedBar != nullptr && !m_selectedMonitorOverride.empty()) {
    selectedMonitorOverride = settings::findMonitorOverride(*selectedBar, m_selectedMonitorOverride);
    if (selectedMonitorOverride == nullptr) {
      m_selectedMonitorOverride.clear();
    }
  }
  settings::RegistryEnvironment env;
  env.niriOverviewSupported = (m_wayland != nullptr && m_wayland->hasNiriOverviewState());
  const auto registry = settings::buildSettingsRegistry(cfg, selectedBar, selectedMonitorOverride, env);
  const auto sections = sectionKeys(registry);
  if (m_selectedSection == "bar" && selectedBar == nullptr) {
    m_selectedSection.clear();
  } else if (m_selectedSection != "bar" && !m_selectedSection.empty() &&
             std::find(sections.begin(), sections.end(), m_selectedSection) == sections.end()) {
    m_selectedSection.clear();
  }
  if (m_selectedSection.empty()) {
    m_selectedSection = std::find(sections.begin(), sections.end(), "appearance") != sections.end()
                            ? std::string("appearance")
                            : (!sections.empty() ? sections.front() : std::string{});
  }
  const std::string resetPageScope = pageScopeKey(m_selectedSection, m_selectedBarName, m_selectedMonitorOverride);
  std::vector<std::vector<std::string>> resetPagePaths;
  if (m_config != nullptr) {
    for (const auto& entry : registry) {
      if (settingEntryBelongsToPage(entry, m_selectedSection, m_selectedBarName, m_selectedMonitorOverride) &&
          m_config->hasOverride(entry.path) && !containsPath(resetPagePaths, entry.path)) {
        resetPagePaths.push_back(entry.path);
      }
    }
  }
  if (m_pendingResetPageScope != resetPageScope) {
    m_pendingResetPageScope.clear();
  }

  m_inputDispatcher.setSceneRoot(nullptr);
  m_mainContainer = nullptr;
  m_panelBackground = nullptr;
  m_sceneRoot = std::make_unique<Node>();
  m_sceneRoot->setSize(w, h);
  m_sceneRoot->setAnimationManager(&m_animations);

  auto bg = std::make_unique<Box>();
  bg->setPanelStyle();
  bg->setRadius(0.0f);
  bg->setPosition(0.0f, 0.0f);
  bg->setSize(w, h);
  m_panelBackground = static_cast<Box*>(m_sceneRoot->addChild(std::move(bg)));

  auto main = std::make_unique<Flex>();
  main->setDirection(FlexDirection::Vertical);
  main->setAlign(FlexAlign::Stretch);
  main->setJustify(FlexJustify::Start);
  main->setGap(Style::spaceMd * scale);
  main->setPadding(Style::spaceLg * scale);
  main->setSize(w, h);

  const float bodyMaxWidth = 1024.0f * scale;
  const auto centeredRow = [&](std::unique_ptr<Flex> child) {
    child->setFlexGrow(1.0f);
    child->setMaxWidth(bodyMaxWidth);
    auto row = std::make_unique<Flex>();
    row->setDirection(FlexDirection::Horizontal);
    row->setAlign(FlexAlign::Stretch);
    row->setJustify(FlexJustify::Center);
    row->addChild(std::move(child));
    return row;
  };

  auto header = std::make_unique<Flex>();
  header->setDirection(FlexDirection::Horizontal);
  header->setAlign(FlexAlign::Center);
  header->setJustify(FlexJustify::SpaceBetween);
  header->setGap(Style::spaceSm * scale);

  auto headerTitle = std::make_unique<Label>();
  headerTitle->setText(i18n::tr("settings.title"));
  headerTitle->setBold(true);
  headerTitle->setFontSize(Style::fontSizeTitle * scale);
  headerTitle->setColor(roleColor(ColorRole::OnSurface));
  headerTitle->setFlexGrow(1.0f);
  header->addChild(std::move(headerTitle));

  auto closeBtn = std::make_unique<Button>();
  closeBtn->setGlyph("close");
  closeBtn->setVariant(ButtonVariant::Default);
  closeBtn->setGlyphSize(Style::fontSizeBody * scale);
  closeBtn->setMinWidth(Style::controlHeightSm * scale);
  closeBtn->setMinHeight(Style::controlHeightSm * scale);
  closeBtn->setPadding(Style::spaceXs * scale);
  closeBtn->setRadius(Style::radiusMd * scale);
  closeBtn->setOnClick([this]() { close(); });
  header->addChild(std::move(closeBtn));

  main->addChild(centeredRow(std::move(header)));

  const auto requestRebuild = [this]() {
    DeferredCall::callLater([this]() {
      if (m_surface == nullptr) {
        return;
      }
      m_rebuildRequested = true;
      m_surface->requestLayout();
    });
  };

  const auto clearStatus = [this]() {
    m_statusMessage.clear();
    m_statusIsError = false;
  };

  const auto setOverride = [this, requestRebuild](std::vector<std::string> path, ConfigOverrideValue value) {
    DeferredCall::callLater([this, path = std::move(path), value = std::move(value), requestRebuild]() mutable {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->setOverride(path, std::move(value))) {
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.write-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto setOverrides =
      [this, requestRebuild](std::vector<std::pair<std::vector<std::string>, ConfigOverrideValue>> overrides) {
        DeferredCall::callLater([this, overrides = std::move(overrides), requestRebuild]() mutable {
          if (m_config == nullptr) {
            return;
          }
          bool changed = false;
          bool failed = false;
          for (auto& [path, value] : overrides) {
            if (m_config->setOverride(path, std::move(value))) {
              changed = true;
            } else {
              failed = true;
            }
          }
          if (failed) {
            m_statusMessage = i18n::tr("settings.batch-write-error");
            m_statusIsError = true;
            requestRebuild();
            return;
          }
          const bool hadStatus = !m_statusMessage.empty();
          m_statusMessage.clear();
          m_statusIsError = false;
          m_pendingResetPageScope.clear();
          if (changed || hadStatus) {
            requestRebuild();
          }
        });
      };

  const auto clearOverride = [this, requestRebuild](std::vector<std::string> path) {
    DeferredCall::callLater([this, path = std::move(path), requestRebuild]() mutable {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->clearOverride(path)) {
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.clear-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto clearOverrides = [this, requestRebuild](std::vector<std::vector<std::string>> paths) {
    DeferredCall::callLater([this, paths = std::move(paths), requestRebuild]() mutable {
      if (m_config == nullptr || paths.empty()) {
        return;
      }

      bool changed = false;
      bool failed = false;
      for (const auto& path : paths) {
        if (m_config->clearOverride(path)) {
          changed = true;
        } else {
          failed = true;
        }
      }

      m_pendingResetPageScope.clear();
      if (failed) {
        m_statusMessage = i18n::tr("settings.reset-page-error");
        m_statusIsError = true;
        requestRebuild();
        return;
      }

      m_statusMessage.clear();
      m_statusIsError = false;
      if (changed) {
        requestRebuild();
      }
    });
  };

  const auto renameWidgetInstance =
      [this, requestRebuild](std::string oldName, std::string newName,
                             std::vector<std::pair<std::vector<std::string>, ConfigOverrideValue>> referenceOverrides) {
        DeferredCall::callLater([this, oldName = std::move(oldName), newName = std::move(newName),
                                 referenceOverrides = std::move(referenceOverrides), requestRebuild]() mutable {
          if (m_config == nullptr) {
            return;
          }

          bool changed = m_config->renameOverrideTable({"widget", oldName}, {"widget", newName});
          if (!changed) {
            m_statusMessage = i18n::tr("settings.rename-widget-error");
            m_statusIsError = true;
            requestRebuild();
            return;
          }
          bool failed = false;
          for (auto& [path, value] : referenceOverrides) {
            if (m_config->setOverride(path, std::move(value))) {
              changed = true;
            } else {
              failed = true;
            }
          }
          if (failed) {
            m_statusMessage = i18n::tr("settings.batch-write-error");
            m_statusIsError = true;
            requestRebuild();
            return;
          }
          m_statusMessage.clear();
          m_statusIsError = false;
          m_pendingResetPageScope.clear();
          if (changed) {
            requestRebuild();
          }
        });
      };

  const auto createBar = [this, requestRebuild](std::string name) {
    DeferredCall::callLater([this, name = std::move(name), requestRebuild]() {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->createBarOverride(name)) {
        m_selectedSection = "bar";
        m_selectedBarName = name;
        m_selectedMonitorOverride.clear();
        m_creatingBarName.clear();
        m_renamingBarName.clear();
        m_pendingDeleteBarName.clear();
        m_creatingMonitorOverrideBarName.clear();
        m_creatingMonitorOverrideMatch.clear();
        m_renamingMonitorOverrideBarName.clear();
        m_renamingMonitorOverrideMatch.clear();
        m_pendingDeleteMonitorOverrideBarName.clear();
        m_pendingDeleteMonitorOverrideMatch.clear();
        m_contentScrollState.offset = 0.0f;
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.create-bar-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto renameBar = [this, requestRebuild](std::string oldName, std::string newName) {
    DeferredCall::callLater([this, oldName = std::move(oldName), newName = std::move(newName), requestRebuild]() {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->renameBarOverride(oldName, newName)) {
        if (m_selectedBarName == oldName) {
          m_selectedBarName = newName;
        }
        m_selectedMonitorOverride.clear();
        m_renamingBarName.clear();
        m_pendingDeleteBarName.clear();
        m_creatingMonitorOverrideBarName.clear();
        m_creatingMonitorOverrideMatch.clear();
        m_renamingMonitorOverrideBarName.clear();
        m_renamingMonitorOverrideMatch.clear();
        m_pendingDeleteMonitorOverrideBarName.clear();
        m_pendingDeleteMonitorOverrideMatch.clear();
        m_contentScrollState.offset = 0.0f;
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.rename-bar-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto deleteBar = [this, requestRebuild](std::string name) {
    DeferredCall::callLater([this, name = std::move(name), requestRebuild]() {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->deleteBarOverride(name)) {
        if (m_selectedBarName == name) {
          m_selectedBarName.clear();
          m_selectedMonitorOverride.clear();
          m_contentScrollState.offset = 0.0f;
        }
        m_renamingBarName.clear();
        m_pendingDeleteBarName.clear();
        m_creatingMonitorOverrideBarName.clear();
        m_creatingMonitorOverrideMatch.clear();
        m_renamingMonitorOverrideBarName.clear();
        m_renamingMonitorOverrideMatch.clear();
        m_pendingDeleteMonitorOverrideBarName.clear();
        m_pendingDeleteMonitorOverrideMatch.clear();
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.delete-bar-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto moveBar = [this, requestRebuild](std::string name, int direction) {
    DeferredCall::callLater([this, name = std::move(name), direction, requestRebuild]() {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->moveBarOverride(name, direction)) {
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.move-bar-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto createMonitorOverride = [this, requestRebuild](std::string barName, std::string match) {
    DeferredCall::callLater([this, barName = std::move(barName), match = std::move(match), requestRebuild]() {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->createMonitorOverride(barName, match)) {
        m_selectedSection = "bar";
        m_selectedBarName = barName;
        m_selectedMonitorOverride = match;
        m_creatingMonitorOverrideBarName.clear();
        m_creatingMonitorOverrideMatch.clear();
        m_renamingMonitorOverrideBarName.clear();
        m_renamingMonitorOverrideMatch.clear();
        m_pendingDeleteMonitorOverrideBarName.clear();
        m_pendingDeleteMonitorOverrideMatch.clear();
        m_contentScrollState.offset = 0.0f;
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.create-monitor-override-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto renameMonitorOverride = [this, requestRebuild](std::string barName, std::string oldMatch,
                                                            std::string newMatch) {
    DeferredCall::callLater([this, barName = std::move(barName), oldMatch = std::move(oldMatch),
                             newMatch = std::move(newMatch), requestRebuild]() {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->renameMonitorOverride(barName, oldMatch, newMatch)) {
        if (m_selectedBarName == barName && m_selectedMonitorOverride == oldMatch) {
          m_selectedMonitorOverride = newMatch;
        }
        m_renamingMonitorOverrideBarName.clear();
        m_renamingMonitorOverrideMatch.clear();
        m_pendingDeleteMonitorOverrideBarName.clear();
        m_pendingDeleteMonitorOverrideMatch.clear();
        m_contentScrollState.offset = 0.0f;
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.rename-monitor-override-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  const auto deleteMonitorOverride = [this, requestRebuild](std::string barName, std::string match) {
    DeferredCall::callLater([this, barName = std::move(barName), match = std::move(match), requestRebuild]() {
      if (m_config == nullptr) {
        return;
      }
      if (m_config->deleteMonitorOverride(barName, match)) {
        if (m_selectedBarName == barName && m_selectedMonitorOverride == match) {
          m_selectedMonitorOverride.clear();
          m_contentScrollState.offset = 0.0f;
        }
        m_renamingMonitorOverrideBarName.clear();
        m_renamingMonitorOverrideMatch.clear();
        m_pendingDeleteMonitorOverrideBarName.clear();
        m_pendingDeleteMonitorOverrideMatch.clear();
        m_statusMessage.clear();
        m_statusIsError = false;
        m_pendingResetPageScope.clear();
        requestRebuild();
        return;
      }
      m_statusMessage = i18n::tr("settings.delete-monitor-override-error");
      m_statusIsError = true;
      requestRebuild();
    });
  };

  auto filters = std::make_unique<Flex>();
  filters->setDirection(FlexDirection::Horizontal);
  filters->setAlign(FlexAlign::Center);
  filters->setJustify(FlexJustify::SpaceBetween);
  filters->setGap(Style::spaceMd * scale);

  auto searchInput = std::make_unique<Input>();
  searchInput->setPlaceholder(i18n::tr("settings.search-placeholder"));
  searchInput->setValue(m_searchQuery);
  searchInput->setFontSize(Style::fontSizeBody * scale);
  searchInput->setControlHeight(Style::controlHeight * scale);
  searchInput->setHorizontalPadding(Style::spaceSm * scale);
  searchInput->setSize(320.0f * scale, Style::controlHeight * scale);
  searchInput->setFlexGrow(1.0f);
  Input* searchInputPtr = searchInput.get();
  searchInput->setOnChange([this, requestRebuild](const std::string& value) {
    m_searchQuery = value;
    m_focusSearchOnRebuild = true;
    m_pendingResetPageScope.clear();
    requestRebuild();
  });
  filters->addChild(std::move(searchInput));

  auto advancedLabel = makeLabel(i18n::tr("settings.badge-advanced"), Style::fontSizeBody * scale,
                                 roleColor(ColorRole::OnSurfaceVariant), false);
  filters->addChild(std::move(advancedLabel));

  auto advancedToggle = std::make_unique<Toggle>();
  advancedToggle->setScale(scale);
  advancedToggle->setChecked(m_showAdvanced);
  advancedToggle->setOnChange([this, requestRebuild](bool value) {
    if (m_config != nullptr && !m_config->setOverride({"shell", "settings_show_advanced"}, value)) {
      m_statusMessage = i18n::tr("settings.write-error");
      m_statusIsError = true;
      requestRebuild();
      return;
    }
    m_showAdvanced = value;
    m_pendingResetPageScope.clear();
    requestRebuild();
  });
  filters->addChild(std::move(advancedToggle));

  auto overriddenLabel = makeLabel(i18n::tr("settings.filter-modified"), Style::fontSizeBody * scale,
                                   roleColor(ColorRole::OnSurfaceVariant), false);
  filters->addChild(std::move(overriddenLabel));

  auto overriddenToggle = std::make_unique<Toggle>();
  overriddenToggle->setScale(scale);
  overriddenToggle->setChecked(m_showOverriddenOnly);
  overriddenToggle->setOnChange([this, requestRebuild](bool value) {
    m_showOverriddenOnly = value;
    m_pendingResetPageScope.clear();
    requestRebuild();
  });
  filters->addChild(std::move(overriddenToggle));

  if (!resetPagePaths.empty()) {
    const bool pendingReset = m_pendingResetPageScope == resetPageScope;
    auto resetPageBtn = std::make_unique<Button>();
    resetPageBtn->setText(pendingReset ? i18n::tr("settings.reset-page-confirm") : i18n::tr("settings.reset-page"));
    resetPageBtn->setVariant(pendingReset ? ButtonVariant::Default : ButtonVariant::Ghost);
    resetPageBtn->setFontSize(Style::fontSizeCaption * scale);
    resetPageBtn->setMinHeight(Style::controlHeightSm * scale);
    resetPageBtn->setPadding(Style::spaceXs * scale, Style::spaceSm * scale);
    resetPageBtn->setRadius(Style::radiusMd * scale);
    resetPageBtn->setOnClick(
        [this, resetPageScope, resetPagePaths, requestRebuild, clearOverrides, pendingReset]() mutable {
          if (!pendingReset) {
            m_pendingResetPageScope = resetPageScope;
            requestRebuild();
            return;
          }
          clearOverrides(std::move(resetPagePaths));
        });
    filters->addChild(std::move(resetPageBtn));
  }

  main->addChild(centeredRow(std::move(filters)));

  if (!m_statusMessage.empty()) {
    auto status = std::make_unique<Flex>();
    status->setDirection(FlexDirection::Horizontal);
    status->setAlign(FlexAlign::Center);
    status->setGap(Style::spaceSm * scale);
    status->setPadding(Style::spaceXs * scale, Style::spaceSm * scale);
    status->setRadius(Style::radiusMd * scale);
    status->setFill(roleColor(m_statusIsError ? ColorRole::Error : ColorRole::Secondary, 0.14f));
    status->setBorder(roleColor(m_statusIsError ? ColorRole::Error : ColorRole::Secondary, 0.45f), Style::borderWidth);

    auto message = makeLabel(m_statusMessage, Style::fontSizeCaption * scale,
                             roleColor(m_statusIsError ? ColorRole::Error : ColorRole::Secondary), true);
    message->setFlexGrow(1.0f);
    status->addChild(std::move(message));

    auto dismiss = std::make_unique<Button>();
    dismiss->setGlyph("close");
    dismiss->setVariant(ButtonVariant::Ghost);
    dismiss->setGlyphSize(Style::fontSizeCaption * scale);
    dismiss->setMinWidth(Style::controlHeightSm * scale);
    dismiss->setMinHeight(Style::controlHeightSm * scale);
    dismiss->setPadding(Style::spaceXs * scale);
    dismiss->setRadius(Style::radiusSm * scale);
    dismiss->setOnClick([clearStatus, requestRebuild]() {
      clearStatus();
      requestRebuild();
    });
    status->addChild(std::move(dismiss));

    main->addChild(centeredRow(std::move(status)));
  }

  const auto clearTransientSettingsState = [this]() {
    m_openWidgetPickerPath.clear();
    m_editingWidgetName.clear();
    m_renamingWidgetName.clear();
    m_pendingDeleteWidgetName.clear();
    m_pendingDeleteWidgetSettingPath.clear();
    m_creatingWidgetType.clear();
    m_creatingBarName.clear();
    m_renamingBarName.clear();
    m_pendingDeleteBarName.clear();
    m_creatingMonitorOverrideBarName.clear();
    m_creatingMonitorOverrideMatch.clear();
    m_renamingMonitorOverrideBarName.clear();
    m_renamingMonitorOverrideMatch.clear();
    m_pendingDeleteMonitorOverrideBarName.clear();
    m_pendingDeleteMonitorOverrideMatch.clear();
    m_pendingResetPageScope.clear();
  };

  auto body = std::make_unique<Flex>();
  body->setDirection(FlexDirection::Horizontal);
  body->setAlign(FlexAlign::Stretch);
  body->setGap(Style::spaceMd * scale);

  auto sidebar = settings::buildSettingsSidebar(settings::SettingsSidebarContext{
      .config = cfg,
      .sections = sections,
      .availableBars = availableBars,
      .scale = scale,
      .contentScrollState = m_contentScrollState,
      .selectedSection = m_selectedSection,
      .selectedBarName = m_selectedBarName,
      .selectedMonitorOverride = m_selectedMonitorOverride,
      .creatingBarName = m_creatingBarName,
      .creatingMonitorOverrideBarName = m_creatingMonitorOverrideBarName,
      .creatingMonitorOverrideMatch = m_creatingMonitorOverrideMatch,
      .clearTransientState = clearTransientSettingsState,
      .requestRebuild = requestRebuild,
      .createBar = createBar,
      .createMonitorOverride = createMonitorOverride,
  });

  body->addChild(std::move(sidebar));

  auto scroll = std::make_unique<ScrollView>();
  scroll->bindState(&m_contentScrollState);
  scroll->setFlexGrow(1.0f);
  scroll->setScrollbarVisible(true);
  scroll->setViewportPaddingH(0.0f);
  scroll->setViewportPaddingV(Style::spaceSm * scale);
  scroll->clearFill();
  scroll->clearBorder();
  auto* content = scroll->content();
  content->setDirection(FlexDirection::Vertical);
  content->setAlign(FlexAlign::Stretch);
  content->setGap(Style::spaceMd * scale);

  settings::addSettingsEntityManagement(
      *content, settings::SettingsEntityEditorContext{
                    .config = cfg,
                    .configService = m_config,
                    .scale = scale,
                    .searchQuery = m_searchQuery,
                    .selectedSection = m_selectedSection,
                    .selectedBar = selectedBar,
                    .selectedMonitorOverride = selectedMonitorOverride,
                    .renamingBarName = m_renamingBarName,
                    .pendingDeleteBarName = m_pendingDeleteBarName,
                    .renamingMonitorOverrideBarName = m_renamingMonitorOverrideBarName,
                    .renamingMonitorOverrideMatch = m_renamingMonitorOverrideMatch,
                    .pendingDeleteMonitorOverrideBarName = m_pendingDeleteMonitorOverrideBarName,
                    .pendingDeleteMonitorOverrideMatch = m_pendingDeleteMonitorOverrideMatch,
                    .requestRebuild = requestRebuild,
                    .renameBar = renameBar,
                    .deleteBar = deleteBar,
                    .moveBar = moveBar,
                    .renameMonitorOverride = renameMonitorOverride,
                    .deleteMonitorOverride = deleteMonitorOverride,
                });

  settings::addSettingsContentSections(*content, registry,
                                       settings::SettingsContentContext{
                                           .config = cfg,
                                           .configService = m_config,
                                           .scale = scale,
                                           .searchQuery = m_searchQuery,
                                           .selectedSection = m_selectedSection,
                                           .selectedBar = selectedBar,
                                           .selectedMonitorOverride = selectedMonitorOverride,
                                           .showAdvanced = m_showAdvanced,
                                           .showOverriddenOnly = m_showOverriddenOnly,
                                           .openWidgetPickerPath = m_openWidgetPickerPath,
                                           .editingWidgetName = m_editingWidgetName,
                                           .pendingDeleteWidgetName = m_pendingDeleteWidgetName,
                                           .pendingDeleteWidgetSettingPath = m_pendingDeleteWidgetSettingPath,
                                           .renamingWidgetName = m_renamingWidgetName,
                                           .creatingWidgetType = m_creatingWidgetType,
                                           .requestRebuild = requestRebuild,
                                           .resetContentScroll = [this]() { m_contentScrollState.offset = 0.0f; },
                                           .setOverride = setOverride,
                                           .setOverrides = setOverrides,
                                           .clearOverride = clearOverride,
                                           .renameWidgetInstance = renameWidgetInstance,
                                       });

  body->addChild(std::move(scroll));
  auto bodyRow = centeredRow(std::move(body));
  bodyRow->setFlexGrow(1.0f);
  main->addChild(std::move(bodyRow));

  main->setSize(w, h);
  main->layout(*m_renderContext);

  m_mainContainer = static_cast<Flex*>(m_sceneRoot->addChild(std::move(main)));

  m_inputDispatcher.setSceneRoot(m_sceneRoot.get());
  m_inputDispatcher.setCursorShapeCallback(
      [this](std::uint32_t serial, std::uint32_t shape) { m_wayland->setCursorShape(serial, shape); });
  if (m_focusSearchOnRebuild && searchInputPtr != nullptr && searchInputPtr->inputArea() != nullptr) {
    m_inputDispatcher.setFocus(searchInputPtr->inputArea());
    m_focusSearchOnRebuild = false;
  }
  m_surface->setSceneRoot(m_sceneRoot.get());
}

bool SettingsWindow::onPointerEvent(const PointerEvent& event) {
  if (!isOpen() || m_surface == nullptr) {
    return false;
  }

  wl_surface* const ws = m_surface->wlSurface();
  const bool onThis = (event.surface != nullptr && event.surface == ws);
  bool consumed = false;

  switch (event.type) {
  case PointerEvent::Type::Enter:
    if (onThis) {
      m_pointerInside = true;
      m_inputDispatcher.pointerEnter(static_cast<float>(event.sx), static_cast<float>(event.sy), event.serial);
    }
    break;
  case PointerEvent::Type::Leave:
    if (onThis) {
      m_pointerInside = false;
      m_inputDispatcher.pointerLeave();
    }
    break;
  case PointerEvent::Type::Motion:
    if (onThis || m_pointerInside) {
      if (onThis) {
        m_pointerInside = true;
      }
      m_inputDispatcher.pointerMotion(static_cast<float>(event.sx), static_cast<float>(event.sy), 0);
      consumed = m_pointerInside;
    }
    break;
  case PointerEvent::Type::Button: {
    const bool pressed = (event.state == 1);
    if (onThis || m_pointerInside) {
      if (onThis) {
        m_pointerInside = true;
      }
      if (pressed) {
        Select::handleGlobalPointerPress(m_inputDispatcher.hoveredArea());
      }
      m_inputDispatcher.pointerButton(static_cast<float>(event.sx), static_cast<float>(event.sy), event.button,
                                      pressed);
      consumed = m_pointerInside;
    }
    break;
  }
  case PointerEvent::Type::Axis:
    if (m_pointerInside) {
      m_inputDispatcher.pointerAxis(static_cast<float>(event.sx), static_cast<float>(event.sy), event.axis,
                                    event.axisSource, event.axisValue, event.axisDiscrete, event.axisValue120,
                                    event.axisLines);
      consumed = true;
    }
    break;
  }

  if (m_sceneRoot != nullptr && (m_sceneRoot->paintDirty() || m_sceneRoot->layoutDirty())) {
    if (m_sceneRoot->layoutDirty()) {
      m_surface->requestLayout();
    } else {
      m_surface->requestRedraw();
    }
  }

  return consumed;
}

void SettingsWindow::onKeyboardEvent(const KeyboardEvent& event) {
  if (!isOpen() || m_config == nullptr) {
    return;
  }
  const auto requestRebuild = [this]() {
    if (m_surface != nullptr) {
      m_rebuildRequested = true;
      m_surface->requestLayout();
    }
  };
  if (event.pressed && m_config->matchesKeybind(KeybindAction::Cancel, event.sym, event.modifiers)) {
    if (!m_openWidgetPickerPath.empty() || !m_editingWidgetName.empty() || !m_creatingWidgetType.empty() ||
        !m_renamingWidgetName.empty() || !m_pendingDeleteWidgetName.empty() ||
        !m_pendingDeleteWidgetSettingPath.empty() || !m_creatingBarName.empty() || !m_renamingBarName.empty() ||
        !m_pendingDeleteBarName.empty() || !m_creatingMonitorOverrideBarName.empty() ||
        !m_renamingMonitorOverrideBarName.empty() || !m_pendingDeleteMonitorOverrideBarName.empty()) {
      m_openWidgetPickerPath.clear();
      m_editingWidgetName.clear();
      m_renamingWidgetName.clear();
      m_pendingDeleteWidgetName.clear();
      m_pendingDeleteWidgetSettingPath.clear();
      m_creatingWidgetType.clear();
      m_creatingBarName.clear();
      m_renamingBarName.clear();
      m_pendingDeleteBarName.clear();
      m_creatingMonitorOverrideBarName.clear();
      m_creatingMonitorOverrideMatch.clear();
      m_renamingMonitorOverrideBarName.clear();
      m_renamingMonitorOverrideMatch.clear();
      m_pendingDeleteMonitorOverrideBarName.clear();
      m_pendingDeleteMonitorOverrideMatch.clear();
      m_contentScrollState.offset = 0.0f;
      requestRebuild();
      return;
    }
    if (Select::closeAnyOpen()) {
      if (m_surface != nullptr) {
        m_surface->requestLayout();
      }
      return;
    }
  }
  m_inputDispatcher.keyEvent(event.sym, event.utf32, event.modifiers, event.pressed, event.preedit);
  if (m_sceneRoot != nullptr && m_surface != nullptr && (m_sceneRoot->paintDirty() || m_sceneRoot->layoutDirty())) {
    if (m_sceneRoot->layoutDirty()) {
      m_surface->requestLayout();
    } else {
      m_surface->requestRedraw();
    }
  }
}

void SettingsWindow::onThemeChanged() {
  if (isOpen()) {
    m_surface->requestRedraw();
  }
}

void SettingsWindow::onFontChanged() {
  if (isOpen()) {
    m_surface->requestLayout();
  }
}
