#pragma once

#include "../helpers/memory/Memory.hpp"
#include "../helpers/math/Math.hpp"
#include "../managers/input/InputManager.hpp"
#include "../config/shared/ConfigErrors.hpp"

#include "supplementary/DragController.hpp"

#include <optional>
#include <expected>

enum eFullscreenMode : int8_t;

namespace Layout {
    class ITarget;
    class CSpace;

    enum eRectCorner : uint8_t {
        CORNER_NONE        = 0,
        CORNER_TOPLEFT     = (1 << 0),
        CORNER_TOPRIGHT    = (1 << 1),
        CORNER_BOTTOMRIGHT = (1 << 2),
        CORNER_BOTTOMLEFT  = (1 << 3),
    };

    inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) {
        const auto CENTER = box.middle();

        if (pos.x < CENTER.x)
            return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT;
        return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT;
    }

    enum eSnapEdge : uint8_t {
        SNAP_INVALID = 0,
        SNAP_UP      = (1 << 0),
        SNAP_DOWN    = (1 << 1),
        SNAP_LEFT    = (1 << 2),
        SNAP_RIGHT   = (1 << 3),
    };

    enum eFullscreenRequestResult : uint8_t {
        FULLSCREEN_REQUEST_DEFAULT = 0,
        FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT,
    };

    class CLayoutManager {
      public:
        CLayoutManager();
        ~CLayoutManager() = default;

        enum eRecalculateMonitorReason : uint8_t {
            RECALCULATE_MONITOR_REASON_UNKNOWN, // when the recalculate monitor reason is unknown or not important to preserve
            RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE,
            RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE,
            RECALCULATE_MONITOR_REASON_TOGGLE_FULLSCREEN,
        };

        void                     newTarget(SP<ITarget> target, SP<CSpace> space);
        void                     removeTarget(SP<ITarget> target);

        void                     changeFloatingMode(SP<ITarget> target);

        void                     beginDragTarget(SP<ITarget> target, eMouseBindMode mode);
        void                     moveMouse(const Vector2D& mousePos);
        void                     resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
        void                     moveTarget(const Vector2D& Δ, SP<ITarget> target);
        void                     setTargetGeom(const CBox& box, SP<ITarget> target); // floats only
        void                     endDragTarget();

        Config::ErrorResult      layoutMsg(const std::string_view& sv);

        eFullscreenRequestResult fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode);

        void                     switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus = true);

        void                     moveInDirection(SP<ITarget> target, const std::string& direction, bool silent = false);

        SP<ITarget>              getNextCandidate(SP<CSpace> space, SP<ITarget> from);

        bool                     isReachable(SP<ITarget> target);

        void                     bringTargetToTop(SP<ITarget> target);

        std::optional<Vector2D>  predictSizeForNewTiledTarget();

        void                     performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<ITarget> target, eMouseBindMode mode, int corner, const Vector2D& beginSize);

        void                     invalidateMonitorGeometries(PHLMONITOR);
        void                     recalculateMonitor(PHLMONITOR, eRecalculateMonitorReason reason = RECALCULATE_MONITOR_REASON_UNKNOWN);

        const UP<Supplementary::CDragStateController>& dragController();

      private:
        UP<Supplementary::CDragStateController> m_dragStateController = makeUnique<Supplementary::CDragStateController>();
    };
}

inline UP<Layout::CLayoutManager> g_layoutManager;
