#pragma once
#include "core/ui/rect.h"
// drag_out — the pure drag-out gesture law plus the OS hand-off's path-list assembly. REAPER
// hit-testing, cursor setting, and outcome execution stay in the shell (panel_drag.cpp +
// instrument_drop_win / arrange_drop_win / drag_out_win).
//
// THE LAW: the class is resolved from what is under the cursor on EVERY move; every transition
// is reversible until release or until the pointer leaves REAPER entirely; the OS hand-off is
// reserved for leaving REAPER, and every REAPER-internal target executes natively on release.
// Do not reintroduce a first-move class lock or a drag-lifetime "blocked" latch.
//
// Path-list assembly: turns armed sample ids into the absolute path list an OS drop carries
// (Windows CF_HDROP / macOS file-list pasteboard) — set algebra only; the shell resolves each id
// to its on-disk bank file. No temp files; copy-only is enforced at the OS layer (drag_out_win).

#include <string>
#include <vector>

namespace reasampler::ui {

// --- Gesture law --------------------------------------------------------------

// The panel's client rect, own client coords, top-left origin. Half-open: [x, x+width) x
// [y, y+height).
using PanelClientRect = Rect;

// Live drag state reduced to what every gesture decision needs. Pre-threshold "armed but not
// yet dragging" is not a drag. Shared with card_drag's in-grid precedence decision.
struct DragState {
    bool dragging = false;        // threshold crossed; a drag is in progress
    bool hasArmedSamples = false; // payload holds >= 1 sample id
};

// What REAPER reports under the pointer, reduced to the surfaces the law distinguishes.
// Produced from GetThingFromPoint's info token by wire::classifyReaperSurface.
enum class ReaperSurface {
    OffReaper,   // not over REAPER at all — the one irreversible exit
    TrackPanel,  // TCP/MCP, ANY sub-element: the WHOLE panel is the instrument hotspot
    FxSurface,   // fx_* — the FX chain and floating-FX windows
    FxEmbed,     // tcp.fxembed / mcp.fxembed — an instance already draws there
    Arrange,     // the timeline
    Other,       // ruler, transport, spacers, docker chrome, unknown future tokens
    Count,       // sentinel, NOT a real surface — tests/test_drag_out.cpp's kAllSurfaces is
                 // pinned against this via static_assert so a 7th surface can't silently skip
                 // the exhaustiveness matrix
};

// The resolved target class. Every value is a DEFINED outcome the shell executes or visibly
// refuses — there is deliberately no "nothing happens" member, which is what makes "no silent
// no-op release" a property of the enumeration rather than of any one call site.
enum class DropClass {
    None,           // no drag under way, or an empty payload — nothing to resolve
    Internal,       // inside the panel client — the bank-to-bank drag, unchanged
    InstrumentDrop, // a track's panel or FX surface — add ReaSampler 9000 preloaded
    ArrangeInsert,  // the timeline — place items at the pointer's track and time
    Refuse,         // a REAPER surface with no defined outcome for this payload — cue it
    OsHandoff,      // the pointer left REAPER — hand the file list to the OS
};

// Everything the law reads. Nothing here is remembered between evaluations: two evaluations at
// the same point with the same payload resolve identically, whatever happened in between.
struct DropContext {
    DragState drag;
    bool singlePayload = false;  // payload holds EXACTLY one capture (arms InstrumentDrop)
    ReaperSurface surface = ReaperSurface::OffReaper;  // only read outside the client rect
    bool haveTrack = false;      // GetThingFromPoint returned a non-null MediaTrack*
};

// Resolves the class for a drag at pointer (px, py) over `client`. Position-and-context only,
// so re-entry into the client always returns Internal and a surface transition always reverses.
DropClass decideDropClass(int px, int py, const PanelClientRect& client, const DropContext& ctx);

// True outside the panel client rect — the same half-open test decideDropClass gates the SDK
// hit-test on. Exported so the shell reads this one pure predicate instead of reimplementing the
// inside-client math inline (core/ui/CLAUDE.md forbids hit-test geometry living in shell code).
bool needsSurfaceProbe(int px, int py, const PanelClientRect& client);

// The pointer cue for a class, so "will this work" is visible BEFORE release. Internal defers
// to card_drag's own cue; OsOwned means the OS drag loop draws the cursor and we must not fight
// it.
enum class DropCue { None, Internal, Instrument, ArrangeInsert, Refuse, OsOwned };

DropCue cueForDropClass(DropClass cls);

// --- Path-list assembly -------------------------------------------------------

// One armed sample reduced to what path assembly needs: the shell-resolved absolute path (empty
// if unresolvable) and whether it exists on disk.
struct ResolvedSample {
    std::string absolutePath;
    bool fileExists = false;
};

// Outcome of assembling the drag's path list. An empty `paths` means nothing draggable — do not
// start a drag.
struct PathList {
    std::vector<std::string> paths;  // de-duped, existing files, first-seen order
    int skippedMissing = 0;          // resolved but file doesn't exist (stale index entry)
    int skippedUnresolved = 0;       // shell couldn't resolve a path at all
    int skippedDuplicate = 0;        // same absolute path seen more than once
};

// Assembles the drag path list from the resolved samples (selection order). Comparison is
// exact-string — the shell normalizes case/slashes upstream if it wants Windows-style dedup.
PathList assemblePathList(const std::vector<ResolvedSample>& resolved);

// --- OS hand-off ordering -----------------------------------------------------

// Whether an empty/unresolvable payload should hand off to the OS at all. This decides ONLY
// the empty-payload third of the failure space — an unresolvable payload must leave the
// internal drag live rather than winding it down (release capture, clear drag state) for a
// hand-off that then never happens, which reads to the user as "the drag did nothing, try
// again". A resolved-but-OS-not-ready hand-off (OLE unavailable, HDROP build failure) is a
// separate, shell-side readiness gate (drag_out_win::canInitiateDragOut) checked BEFORE the
// shell tears down internal drag state — this struct does not model that path.
struct OsHandoff {
    bool handOffToOs = false;  // true: wind down internal drag state, then start the OS drag
};

// Decides the hand-off from the assembled path list. Empty (everything stale/unresolvable)
// means the internal drag stays live rather than dying half-torn-down.
OsHandoff decideOsHandoff(const std::vector<std::string>& paths);

} // namespace reasampler::ui
