#pragma once
// card_drag — the REAPER-free decision logic behind the L7 in-grid reorder drag. Three
// pure concerns live here so they are unit-tested outside the DAW (CLAUDE.md §load-bearing
// split); the SWELL wiring, SetCursor call, cursor resources, and drop-target draw stay in
// the shell (bank_panel.cpp). Mirror of drag_out::decideGesture.
//
//   1. GESTURE PRECEDENCE (F3 settled). A live drag resolves to exactly one gesture, in a
//      strict precedence the shell evaluates on every mouse-move / at drop:
//        (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 (Alt-replace-over-occupied)
//      So leave-client wins first, then other-bank, then same-bank-grid = reorder/replace.
//      This keeps the reorder gesture from ever stealing a bank-move or OS-drag.
//
//   2. SLOT HIT-TEST. Which grid SLOT a pointer sits over, sparse-aware: the grid tiles
//      slots 0..maxSlot including empty ones, so hit-testing maps a point to a slot index
//      (empty or occupied) or -1 for a miss. The pixel<->slot rect math extends bank_grid's
//      dense tiling to the gap-preserving slot layout.
//
//   3. DROP-RESULT -> CURSOR CUE. The resolved gesture maps to a cursor cue enum the shell
//      turns into a SetCursor call. The cue DECISION is pure (here); the shell owns only
//      the SetCursor call and the cursor resources. The Replace cue appears ONLY when Alt
//      is actually held over an occupied slot (precedence rule 3's Alt branch).
//
// PURE MODULE: NO REAPER types, NO SWELL, NO LICE, NO OS, NO vendor/ includes. Standard
// library only. Reuses drag_out's PanelClientRect / DragState and bank_grid's CellRect.

#include <vector>

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

namespace reasampler {

// Which drop region the pointer currently sits over WITHIN the client rect. The shell
// classifies the live pointer against its own region geometry (tab strip / other bank
// region / this bank's own grid) and passes the verdict; card_drag does not know panel
// layout, only the precedence over these verdicts. (When the pointer has left the client
// rect the shell need not compute this — OsDragOut wins first regardless.)
enum class DropRegion {
    SameBankGrid,   // over the dragged samples' OWN bank grid — a reorder/replace target
    OtherBankOrTab, // over a tab or the other region's bank — a move/copy target
    DeadSpace,      // inside the client but over no drop target (header, footer, gap)
};

// The resolved gesture — one clean outcome the shell acts on and maps to a cursor.
enum class CardGesture {
    None,       // no drag under way, or an empty payload — do nothing
    OsDragOut,  // pointer left the client rect — hand off to the native OS drag (drag_out)
    Move,       // drop over another bank/tab, no Ctrl — move the samples there
    Copy,       // drop over another bank/tab, Ctrl held — copy the samples there
    Reorder,    // drop within the same bank grid — reorder to the target slot
    Replace,    // drop within the same bank grid, Alt over an OCCUPIED slot — replace
};

// The live drag inputs the precedence decision needs beyond position + client rect:
//   region       — the shell's verdict on what the pointer sits over (see DropRegion).
//   targetSlot   — the slot the pointer sits over in the same-bank grid, or -1 (used only
//                  when region == SameBankGrid to decide empty-vs-occupied).
//   slotOccupied — whether targetSlot currently holds a sample (drives Reorder vs Replace).
//   ctrl         — Ctrl held (Copy vs Move over another bank).
//   alt          — Alt held (Replace vs Reorder over an occupied same-bank slot).
struct DragModifiers {
    DropRegion region = DropRegion::DeadSpace;
    int  targetSlot   = -1;
    bool slotOccupied = false;
    bool ctrl = false;
    bool alt  = false;
};

// Resolves the gesture for a drag at pointer (px, py) over `client`, given the drag
// `state` and the live `mods`. Precedence exactly as documented above.
//   * Not dragging / no armed samples: None.
//   * Pointer OUTSIDE the client rect: OsDragOut (wins first — invariant #4 boundary).
//   * OtherBankOrTab: Copy if ctrl else Move.
//   * SameBankGrid: Replace iff (alt AND the target slot is occupied); else Reorder
//     (whether the slot is empty — place — or occupied without Alt — insert-shift).
//   * DeadSpace inside the client: None (a drop here is a no-op).
CardGesture decideCardGesture(int px, int py, const PanelClientRect& client,
                              const DragState& state, const DragModifiers& mods);

// The cursor cue the shell should show for a resolved gesture. 1:1 with CardGesture but
// named as a cursor concern so the shell maps it to a SetCursor resource. None -> the
// default arrow. The Replace cue is produced ONLY for CardGesture::Replace (which itself
// requires Alt-over-occupied), satisfying "the replace cursor appears only while Alt is
// held over an occupied slot."
enum class CursorCue {
    Default,   // arrow — no drag, or dead space
    Reorder,   // within-bank reorder
    Move,      // move to another bank/tab
    Copy,      // copy to another bank/tab
    OsDragOut, // pointer left the client (the OS drag loop owns the cursor once handed off)
    Replace,   // Alt-replace over an occupied slot
};

// Maps a resolved gesture to its cursor cue (pure — the shell owns SetCursor only).
CursorCue cursorForGesture(CardGesture g);

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

// The pixel rect of one grid SLOT (empty or occupied). Distinct from bank_grid's CellRect
// only in intent — a SlotCellRect carries the slot index it draws, so the shell can map a
// drawn/hit rect back to the model slot without a parallel array. width/height match the
// grid spec; (x, y) is the top-left in the region's grid-viewport coordinates (the shell
// translates by the grid origin exactly as regionCellRects does today).
struct SlotCellRect {
    int slot = 0;   // the model slot this rect represents (0..maxSlot)
    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) into a panel of the given pixel width, honoring the
// grid spec — the sparse-aware sibling of bank_grid::computeCellRects. Every slot in
// [0, maxSlot] gets a rect (empty slots included) so a gap draws as an empty cell and a
// drop targets it precisely. `maxSlot` < 0 -> empty (no occupied slots). The rects use the
// SAME column/row math as computeCellRects (slot index in place of item index), so an
// all-dense map (slots 0..N-1) lays out identically to today's grid.
std::vector<SlotCellRect> computeSlotRects(int maxSlot, int panelWidth,
                                           const GridSpec& spec);

// Like computeSlotRects but extends one full trailing row of slots beyond maxSlot so a
// drop pointer past the last occupied card still resolves to a valid target slot. The
// trailing slots (maxSlot+1 .. maxSlot+cols) are empty — a drop on any of them calls
// reorderSample with that slot index, which places the card there directly (no shift,
// because the slot is empty). Used ONLY for drop hit-testing; the draw path uses
// computeSlotRects (no trailing ghost row in the visual).
// When maxSlot < 0 the trailing row starts at slot 0 (same as a fresh bank with no cards).
std::vector<SlotCellRect> computeSlotRectsForDrop(int maxSlot, int panelWidth,
                                                   const GridSpec& spec);

// Hit-tests a point against slot rects (half-open bounds, matching hitTestCell). Returns
// the SLOT index (rect.slot) of the first rect containing the point, or -1 on a miss (gap,
// margin, below the last row). NOTE the return is the slot index, NOT the vector index —
// callers reason in model slots.
int hitTestSlot(int px, int py, const std::vector<SlotCellRect>& rects);

} // namespace reasampler
