#pragma once
// card_drag — decision logic behind the in-grid reorder drag. Mirror of drag_out::decideGesture;
// SWELL wiring, SetCursor, cursor resources, and drop-target draw stay in the shell.
//
// Gesture precedence, evaluated on every mouse-move / at drop, strict order:
//   (1) pointer LEFT the client rect          -> OsDragOut (hand off to the OS)
//   (2) else drop over a tab / the OTHER bank -> Move | Copy (Ctrl = Copy)
//   (3) else drop within the SAME bank's grid -> Reorder | Replace
//         - empty slot                        -> Reorder (place there)
//         - occupied slot, no modifier         -> Reorder (insert-before-and-shift)
//         - occupied slot, Alt held            -> Replace
// Leave-client wins first, then other-bank, then same-bank-grid — so reorder can never steal a
// bank-move or an OS-drag.
//
// Also owns: sparse-aware slot hit-test (a point -> grid slot, empty or occupied, extending
// bank_grid's dense tiling), and the gesture -> cursor-cue mapping (Replace's cue appears only
// when Alt is actually held over an occupied slot).

#include <vector>

#include "core/ui/bank_grid.h"   // CellRect
#include "core/ui/drag_out.h"    // PanelClientRect, DragState

namespace reasampler::ui {

// Which drop region the pointer sits over within the client rect; the shell classifies against
// its own region geometry and passes the verdict — card_drag knows only precedence over these.
enum class DropRegion {
    SameBankGrid,   // over the dragged samples' OWN bank grid — reorder/replace target
    OtherBankOrTab, // over a tab or the other region's bank — move/copy target
    DeadSpace,      // inside the client but over no drop target
};

// The resolved gesture the shell acts on and maps to a cursor.
enum class CardGesture {
    None,
    OsDragOut,
    Move,
    Copy,
    Reorder,
    Replace,
};

// Live drag inputs the precedence decision needs beyond position + client rect.
struct DragModifiers {
    DropRegion region = DropRegion::DeadSpace;
    int  targetSlot   = -1;    // slot under the pointer in SameBankGrid; -1 otherwise
    bool slotOccupied = false; // drives Reorder vs Replace
    bool ctrl = false;         // Copy vs Move over another bank
    bool alt  = false;         // Replace vs Reorder over an occupied same-bank slot
};

// Resolves the gesture for a drag at pointer (px, py) over `client`. See precedence above.
CardGesture decideCardGesture(int px, int py, const PanelClientRect& client,
                              const DragState& state, const DragModifiers& mods);

enum class CursorCue {
    Default,
    Reorder,
    Move,
    Copy,
    OsDragOut,
    Replace,
};

CursorCue cursorForGesture(CardGesture g);

// --- Sparse-aware slot layout + hit-test --------------------------------------

// One grid SLOT's pixel rect (empty or occupied); carries its slot index so the shell can map a
// rect back to the model slot without a parallel array.
struct SlotCellRect {
    int slot = 0;
    int x = 0;
    int y = 0;
    int width = 0;
    int height = 0;

    bool operator==(const SlotCellRect& o) const {
        return slot == o.slot && x == o.x && y == o.y &&
               width == o.width && height == o.height;
    }
};

// Tiles slots [0, maxSlot] inclusive (empty slots included, so a gap draws and a drop targets it
// precisely). maxSlot < 0 -> empty. Same column/row math as bank_grid::computeCellRects.
std::vector<SlotCellRect> computeSlotRects(int maxSlot, int panelWidth,
                                           const GridSpec& spec);

// Like computeSlotRects but extends one full trailing row past maxSlot so a drop pointer beyond
// the last occupied card still resolves to a valid (empty) target slot. Drop hit-testing only —
// the draw path uses computeSlotRects, no ghost row in the visual.
std::vector<SlotCellRect> computeSlotRectsForDrop(int maxSlot, int panelWidth,
                                                   const GridSpec& spec);

// Slot index (rect.slot, NOT the vector index) of the first rect containing the point, or -1 on
// a miss. Half-open bounds, matching hitTestCell.
int hitTestSlot(int px, int py, const std::vector<SlotCellRect>& rects);

} // namespace reasampler::ui
