#pragma once

#include "wayland/ext_foreign_toplevels.h"
#include "wayland/output_scale.h"
#include "wayland/wayland_seat.h"
#include "wayland/wayland_toplevels.h"

#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <poll.h>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>

struct wl_compositor;
struct wl_display;
struct wl_output;
struct wl_registry;
struct wl_seat;
struct wl_shm;
struct wl_subcompositor;
struct zwlr_layer_shell_v1;
struct zwlr_layer_surface_v1;
struct zxdg_output_manager_v1;
struct zxdg_output_v1;
struct xdg_wm_base;
struct wp_cursor_shape_manager_v1;
struct ext_idle_notifier_v1;
struct ext_idle_notification_v1;
struct zwp_idle_inhibit_manager_v1;
struct ext_background_effect_manager_v1;
struct xdg_activation_v1;
struct ext_session_lock_manager_v1;
struct zwlr_foreign_toplevel_manager_v1;
struct zwlr_foreign_toplevel_handle_v1;
struct ext_workspace_manager_v1;
struct org_kde_plasma_virtual_desktop_management;
struct zdwl_ipc_manager_v2;
struct zwp_virtual_keyboard_manager_v1;
struct zwp_text_input_manager_v3;
struct hyprland_focus_grab_manager_v1;
struct hyprland_toplevel_mapping_manager_v1;
struct zwlr_gamma_control_manager_v1;
struct zwlr_screencopy_manager_v1;
struct wp_fractional_scale_manager_v1;
struct wp_viewporter;
struct zwlr_output_manager_v1;
struct zwlr_output_head_v1;
struct zwlr_output_mode_v1;
class ClipboardService;
class FocusGrabService;
struct DataControlOps;
class TextInputService;
class VirtualKeyboardService;

struct WaylandOutput {
  std::uint32_t name = 0;
  std::string interfaceName;
  std::string connectorName;
  std::string description;
  // From wlr-output-management-unstable-v1; wl_output has no serial. May stay empty.
  std::string make;
  std::string model;
  std::string serialNumber;
  std::uint32_t version = 0;
  wl_output* output = nullptr;
  std::int32_t scale = 1;
  std::int32_t width = 0;
  std::int32_t height = 0;
  std::int32_t logicalWidth = 0;
  std::int32_t logicalHeight = 0;
  std::int32_t logicalX = 0;
  std::int32_t logicalY = 0;
  std::int32_t transform = 0;
  zxdg_output_v1* xdgOutput = nullptr;
  bool done = false;
  // wlr-output-management fixed scale factor for this output (0 == not reported).
  double headScaleFactor = 0.0;
  // Canonical published render scale as a /120 numerator (120 == 1.0). Resolved
  // from head scale, then mode/logical ratio, then integer wl_output.scale.
  std::int32_t configuredScaleNumerator = wayland::kScaleNumeratorBase;

  [[nodiscard]] float configuredScale() const noexcept {
    return static_cast<float>(configuredScaleNumerator) / static_cast<float>(wayland::kScaleNumeratorBase);
  }

  [[nodiscard]] std::int32_t effectiveLogicalWidth() const noexcept {
    if (logicalWidth > 0) {
      return logicalWidth;
    }
    if (width <= 0 || scale <= 0) {
      return 0;
    }
    const std::int32_t scaled = width / scale;
    return scaled > 0 ? scaled : 1;
  }

  [[nodiscard]] std::int32_t effectiveLogicalHeight() const noexcept {
    if (logicalHeight > 0) {
      return logicalHeight;
    }
    if (height <= 0 || scale <= 0) {
      return 0;
    }
    const std::int32_t scaled = height / scale;
    return scaled > 0 ? scaled : 1;
  }

  [[nodiscard]] bool hasUsableGeometry() const noexcept {
    return effectiveLogicalWidth() > 0 && effectiveLogicalHeight() > 0;
  }
};

// Accumulates head fields until matched to a WaylandOutput, keyed by head proxy.
struct WaylandOutputHeadInfo {
  std::string name;
  std::string make;
  std::string model;
  std::string serialNumber;
  // wlr-output-management fixed scale factor (0 == not reported by this head).
  double scaleFactor = 0.0;
};

class WaylandConnection {
public:
  WaylandConnection();
  ~WaylandConnection();

  WaylandConnection(const WaylandConnection&) = delete;
  WaylandConnection& operator=(const WaylandConnection&) = delete;

  using ChangeCallback = std::function<void()>;

  bool connect();

  // Delegate setters
  void setOutputChangeCallback(ChangeCallback callback);
  void setOutputLifecycleCallbacks(std::function<void(wl_output*)> added, std::function<void(wl_output*)> removed);
  void setWorkspaceManagerCallbacks(
      std::function<void(ext_workspace_manager_v1*)> extWorkspace, std::function<void(zdwl_ipc_manager_v2*)> dwlIpc
  );
  void setKdeVirtualDesktopManagerCallback(std::function<void(org_kde_plasma_virtual_desktop_management*)> callback);
  void setToplevelChangeCallback(ChangeCallback callback);
  void setHyprlandToplevelMappingManagerCallback(
      std::function<void(hyprland_toplevel_mapping_manager_v1* manager)> callback
  );
  void setPointerEventCallback(WaylandSeat::PointerEventCallback callback);
  void setKeyboardEventCallback(WaylandSeat::KeyboardEventCallback callback);
  void setLockKeysChangeCallback(WaylandSeat::LockKeysChangeCallback callback);
  /// Fired when both `ext_idle_notifier_v1` and `wl_seat` are bound (including late registry globals).
  void setIdleCapabilitiesReadyCallback(ChangeCallback callback);
  void setClipboardService(ClipboardService* clipboardService);
  void setTextInputService(TextInputService* textInputService);
  void setVirtualKeyboardService(VirtualKeyboardService* virtualKeyboardService);
  void setCursorShape(std::uint32_t serial, std::uint32_t shape);

  [[nodiscard]] int repeatPollTimeoutMs() const;
  void repeatTick();
  void stopKeyRepeat();

  // Queries
  [[nodiscard]] bool isConnected() const noexcept;
  [[nodiscard]] bool hasRequiredGlobals() const noexcept;
  [[nodiscard]] bool hasLayerShell() const noexcept;
  [[nodiscard]] bool hasSubcompositor() const noexcept;
  [[nodiscard]] bool hasXdgOutputManager() const noexcept;
  [[nodiscard]] bool hasXdgShell() const noexcept;
  [[nodiscard]] bool hasExtWorkspaceManager() const noexcept;
  [[nodiscard]] bool hasKdeVirtualDesktopManager() const noexcept;
  [[nodiscard]] bool hasDwlIpcManager() const noexcept;
  [[nodiscard]] bool hasForeignToplevelManager() const noexcept;
  [[nodiscard]] bool hasExtForeignToplevelList() const noexcept;
  [[nodiscard]] bool hasSessionLockManager() const noexcept;
  [[nodiscard]] bool hasIdleNotifier() const noexcept;
  [[nodiscard]] bool hasIdleInhibitManager() const noexcept;
  [[nodiscard]] bool hasFractionalScale() const noexcept;
  [[nodiscard]] bool hasGammaControl() const noexcept;
  [[nodiscard]] bool hasOutputManagement() const noexcept;
  [[nodiscard]] bool hasScreencopy() const noexcept;
  [[nodiscard]] zwlr_screencopy_manager_v1* screencopyManager() const noexcept;
  [[nodiscard]] bool hasBackgroundEffectBlur() const noexcept;
  [[nodiscard]] zwlr_gamma_control_manager_v1* gammaControlManager() const noexcept;
  [[nodiscard]] ext_background_effect_manager_v1* backgroundEffectManager() const noexcept;
  [[nodiscard]] wp_fractional_scale_manager_v1* fractionalScaleManager() const noexcept;
  [[nodiscard]] hyprland_focus_grab_manager_v1* hyprlandFocusGrabManager() const noexcept;
  [[nodiscard]] FocusGrabService* focusGrabService() const noexcept;
  [[nodiscard]] TextInputService* textInputService() const noexcept { return m_textInputService; }
  [[nodiscard]] wp_viewporter* viewporter() const noexcept;
  [[nodiscard]] wl_display* display() const noexcept;
  [[nodiscard]] std::string describeDisplayError(int operationErrno = 0) const;
  [[nodiscard]] wl_compositor* compositor() const noexcept;
  [[nodiscard]] wl_seat* seat() const noexcept;
  [[nodiscard]] wl_shm* shm() const noexcept;
  [[nodiscard]] wl_subcompositor* subcompositor() const noexcept;
  [[nodiscard]] zwlr_layer_shell_v1* layerShell() const noexcept;
  [[nodiscard]] xdg_wm_base* xdgWmBase() const noexcept;
  [[nodiscard]] ext_session_lock_manager_v1* sessionLockManager() const noexcept;
  [[nodiscard]] ext_idle_notifier_v1* idleNotifier() const noexcept;
  /// Inhibitor-aware idle notification (`get_idle_notification`); honors `zwp_idle_inhibitor_v1`.
  [[nodiscard]] ext_idle_notification_v1* createIdleNotification(std::uint32_t timeoutMs) const;
  [[nodiscard]] zwp_idle_inhibit_manager_v1* idleInhibitManager() const noexcept;
  [[nodiscard]] const std::vector<WaylandOutput>& outputs() const noexcept;
  [[nodiscard]] WaylandOutput* findOutputByWl(wl_output* wlOutput);
  [[nodiscard]] const WaylandOutput* findOutputByWl(wl_output* wlOutput) const;
  [[nodiscard]] WaylandOutput* findOutputByXdg(zxdg_output_v1* xdgOutput);

  [[nodiscard]] bool hasXdgActivation() const noexcept;
  [[nodiscard]] std::string requestActivationToken(wl_surface* surface) const;
  void activateSurface(wl_surface* surface);
  void activateToplevelForAppId(std::string_view appId);

  [[nodiscard]] std::optional<ActiveToplevel> activeToplevel() const;
  [[nodiscard]] std::optional<ActiveToplevel>
  matchToplevelByTitleAndAppId(std::string_view title, std::string_view appId, wl_output* preferredOutput) const;
  [[nodiscard]] wl_output* activeToplevelOutput() const;
  [[nodiscard]] std::vector<std::string> runningAppIds(wl_output* outputFilter = nullptr) const;
  [[nodiscard]] std::vector<ToplevelInfo>
  windowsForApp(const std::string& idLower, const std::string& wmClassLower, wl_output* outputFilter = nullptr) const;
  [[nodiscard]] std::vector<ToplevelInfo> windowsWithoutAppId(wl_output* outputFilter = nullptr) const;
  [[nodiscard]] std::vector<ToplevelInfo>
  extWindowsForApp(const std::string& idLower, const std::string& wmClassLower) const;
  [[nodiscard]] std::vector<ToplevelInfo> extWindowsWithoutAppId() const;
  [[nodiscard]] bool containsWlrToplevelHandle(zwlr_foreign_toplevel_handle_v1* handle) const;
  template <typename Fn> void visitExtToplevelHandles(Fn&& fn) const {
    m_extForeignToplevels.visitExtHandles(std::forward<Fn>(fn));
  }
  void activateToplevel(zwlr_foreign_toplevel_handle_v1* handle);
  void closeToplevel(zwlr_foreign_toplevel_handle_v1* handle);
  template <typename Fn> void visitWlrToplevelHandles(Fn&& fn) const {
    m_toplevelsHandler.visitWlrHandles(std::forward<Fn>(fn));
  }
  template <typename Fn> void visitWlrToplevels(Fn&& fn) const {
    m_toplevelsHandler.visitWlrToplevels(std::forward<Fn>(fn));
  }
  [[nodiscard]] wl_output* lastPointerOutput() const noexcept;
  [[nodiscard]] wl_surface* lastPointerSurface() const noexcept;
  [[nodiscard]] wl_surface* lastKeyboardSurface() const noexcept;
  [[nodiscard]] bool hasPointerPosition() const noexcept;
  [[nodiscard]] double lastPointerX() const noexcept;
  [[nodiscard]] double lastPointerY() const noexcept;
  [[nodiscard]] WaylandSeat::InputSource lastInputSource() const noexcept;
  [[nodiscard]] std::string currentKeyboardLayoutName() const;
  [[nodiscard]] std::vector<std::string> keyboardLayoutNames() const;
  [[nodiscard]] WaylandSeat::LockKeysState keyboardLockKeysState() const;
  [[nodiscard]] std::uint32_t lastInputSerial() const noexcept;
  [[nodiscard]] double userIdleSeconds() const noexcept;
  [[nodiscard]] bool hasFreshPointerOutput(std::chrono::milliseconds maxAge) const noexcept;
  [[nodiscard]] wl_output* outputForSurface(wl_surface* surface) const noexcept;

  void registerSurfaceOutput(wl_surface* surface, wl_output* output);
  void notifySurfaceOutputEnter(wl_surface* surface, wl_output* output);
  void notifySurfaceOutputLeave(wl_surface* surface, wl_output* output);
  void registerLayerSurface(wl_surface* surface, zwlr_layer_surface_v1* layerSurface);
  void unregisterSurface(wl_surface* surface);
  [[nodiscard]] zwlr_layer_surface_v1* layerSurfaceFor(wl_surface* surface) const noexcept;
  void notifyOutputReady(wl_output* output);

  // Registry listener entrypoints
  static void
  handleGlobal(void* data, wl_registry* registry, std::uint32_t name, const char* interface, std::uint32_t version);
  static void handleGlobalRemove(void* data, wl_registry* registry, std::uint32_t name);

  void onBackgroundEffectCapabilities(std::uint32_t capabilities) noexcept;
  void notifyIdleCapabilitiesReady();

  // wlr-output-management-unstable-v1 entrypoints
  void onOutputManagerHead(zwlr_output_head_v1* head);
  void onOutputManagerDone();
  void onOutputManagerFinished(zwlr_output_manager_v1* manager);
  void onOutputHeadName(zwlr_output_head_v1* head, const char* name);
  void onOutputHeadMake(zwlr_output_head_v1* head, const char* make);
  void onOutputHeadModel(zwlr_output_head_v1* head, const char* model);
  void onOutputHeadSerialNumber(zwlr_output_head_v1* head, const char* serialNumber);
  void onOutputHeadMode(zwlr_output_head_v1* head, zwlr_output_mode_v1* mode);
  void onOutputHeadScale(zwlr_output_head_v1* head, double scaleFactor);
  void onOutputModeFinished(zwlr_output_mode_v1* mode);
  void onOutputHeadFinished(zwlr_output_head_v1* head);
  // wl_output.name and the done event race; call from both sides to match either order.
  void matchPendingOutputHeads();
  // Recompute out.configuredScaleNumerator from current head/mode/logical/wl-scale
  // data. Returns true when the published numerator changed.
  bool recomputeConfiguredScale(WaylandOutput& out);

private:
  void bindGlobal(wl_registry* registry, std::uint32_t name, const char* interface, std::uint32_t version);
  void bindClipboardService();
  void bindTextInputService();
  void bindVirtualKeyboardService();
  void cleanup();
  void logStartupSummary() const;

  wl_display* m_display = nullptr;
  wl_registry* m_registry = nullptr;
  wl_compositor* m_compositor = nullptr;
  wl_seat* m_seat = nullptr;
  wl_shm* m_shm = nullptr;
  wl_subcompositor* m_subcompositor = nullptr;
  zwlr_layer_shell_v1* m_layerShell = nullptr;
  zxdg_output_manager_v1* m_xdgOutputManager = nullptr;
  xdg_wm_base* m_xdgWmBase = nullptr;
  wp_cursor_shape_manager_v1* m_cursorShapeManager = nullptr;
  xdg_activation_v1* m_xdgActivation = nullptr;
  ext_session_lock_manager_v1* m_sessionLockManager = nullptr;
  ext_idle_notifier_v1* m_idleNotifier = nullptr;
  zwp_idle_inhibit_manager_v1* m_idleInhibitManager = nullptr;
  ext_background_effect_manager_v1* m_backgroundEffectManager = nullptr;
  wp_fractional_scale_manager_v1* m_fractionalScaleManager = nullptr;
  hyprland_focus_grab_manager_v1* m_hyprlandFocusGrabManager = nullptr;
  zwlr_gamma_control_manager_v1* m_gammaControlManager = nullptr;
  zwlr_screencopy_manager_v1* m_screencopyManager = nullptr;
  zwlr_output_manager_v1* m_outputManager = nullptr;
  std::unordered_map<zwlr_output_head_v1*, WaylandOutputHeadInfo> m_outputHeads;
  std::unordered_set<zwlr_output_mode_v1*> m_outputModes;
  std::unique_ptr<FocusGrabService> m_focusGrabService;
  wp_viewporter* m_viewporter = nullptr;
  bool m_backgroundEffectBlurSupported = false;
  void* m_dataControlManager = nullptr;
  const DataControlOps* m_dataControlOps = nullptr;
  zwp_text_input_manager_v3* m_textInputManager = nullptr;
  zwp_virtual_keyboard_manager_v1* m_virtualKeyboardManager = nullptr;
  ClipboardService* m_clipboardService = nullptr;
  TextInputService* m_textInputService = nullptr;
  VirtualKeyboardService* m_virtualKeyboardService = nullptr;
  bool m_hasLayerShellGlobal = false;
  bool m_hasExtWorkspaceGlobal = false;
  bool m_hasKdeVirtualDesktopGlobal = false;
  bool m_hasDwlIpcGlobal = false;
  bool m_hasForeignToplevelManagerGlobal = false;
  bool m_hasExtForeignToplevelListGlobal = false;
  std::vector<WaylandOutput> m_outputs;
  ChangeCallback m_outputChangeCallback;
  ChangeCallback m_idleCapabilitiesReadyCallback;
  std::function<void(wl_output*)> m_outputAddedCallback;
  std::function<void(wl_output*)> m_outputRemovedCallback;
  std::function<void(ext_workspace_manager_v1*)> m_extWorkspaceManagerCallback;
  std::function<void(org_kde_plasma_virtual_desktop_management*)> m_kdeVirtualDesktopManagerCallback;
  std::function<void(zdwl_ipc_manager_v2*)> m_dwlIpcManagerCallback;
  std::function<void(hyprland_toplevel_mapping_manager_v1*)> m_hyprlandToplevelMappingManagerCallback;
  std::unordered_map<wl_surface*, wl_output*> m_surfaceOutputMap;
  std::unordered_map<wl_surface*, std::vector<wl_output*>> m_surfaceOutputs;
  std::unordered_map<wl_surface*, zwlr_layer_surface_v1*> m_layerSurfaceMap;
  wl_output* m_lastPointerOutput = nullptr;
  std::chrono::steady_clock::time_point m_lastPointerOutputAt;
  WaylandSeat::PointerEventCallback m_pointerEventCallback;

  WaylandSeat m_seatHandler;
  WaylandToplevels m_toplevelsHandler;
  WaylandExtForeignToplevels m_extForeignToplevels;
};
