#pragma once
// panel_state — INTERNAL shared state + cross-seam contract of the docked bank panel.
// Included ONLY by the panel's own TUs; outside consumers use the per-seam public
// headers (panel_window.h / panel_input.h / panel_bank_ops.h / panel_layout.h).
// Cross-seam calls are plain free functions — direct call-through, no virtual dispatch
// (audition and per-mouse-move paths must stay direct calls).
//
// REFERENCE-INVALIDATION GUARDRAIL: a bank-structural mutation (create/delete/
// evacuate/activate/move) can reallocate the book's vector, so a BankModel& / Bank*
// must NEVER be cached across one. Every seam resolves fresh after any mutation and
// passes bank IDS (not references) into the model ops.

#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

// On macOS/Linux SWELL is provided by the host (SWELL_PROVIDED_BY_APP); on Windows
// we use native Win32 (windows.h first, then swell.h no-ops on _WIN32).
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
#include "wdltypes.h"
#include "swell/swell.h"

// REAPER API function pointers are declared per-TU; main.cpp owns the definitions.
#include "reaper_plugin.h"

#include "core/audio/peaks.h"
#include "core/capture/capture_paths.h"
#include "core/capture/render_settings.h"
#include "core/capture/tail_control.h"
#include "core/model/bank_book.h"
#include "core/model/bank_model.h"
#include "core/ui/action_bar.h"
#include "core/ui/bank_grid.h"
#include "core/ui/card_drag.h"
#include "core/ui/card_meta.h"
#include "core/ui/component_geometry.h"
#include "core/ui/drag_out.h"
#include "core/ui/footer_bar.h"
#include "core/ui/mode_enable.h"
#include "core/ui/overflow_menu.h"
#include "core/ui/prune_button.h"
#include "core/ui/tab_strip.h"
#include "core/ui/theme.h"
#include "core/ui/tooltip.h"
#include "core/version/app_version.h"
#include "core/view/guid_diff.h"
#include "core/view/lane_keys.h"
#include "core/view/mode_switch.h"
#include "core/wire/instrument_drop.h"

#include "shell/panel/panel_layout.h"

namespace reasampler {
class ReaSamplerSession;
}

namespace reasampler::panel {

// core/ui
using ui::ActionBarRect;
using ui::ActionBarSlot;
using ui::ActionBarSpec;
using ui::ActionCluster;
using ui::ButtonRect;
using ui::CardGesture;
using ui::CellRect;
using ui::ClusterSpec;
using ui::CursorCue;
using ui::DragModifiers;
using ui::DragState;
using ui::DropClass;
using ui::DropContext;
using ui::DropCue;
using ui::DropRegion;
using ui::FooterBarLayout;
using ui::FooterBarSpec;
using ui::FooterHit;
using ui::FooterRect;
using ui::GridSpec;
using ui::InteractionState;
using ui::KitBox;
using ui::KitButtonBox;
using ui::KitColor;
using ui::MenuBarRect;
using ui::MenuButtonRect;
using ui::MenuButtonSpec;
using ui::MusicalLength;
using ui::NavKey;
using ui::PanelClientRect;
using ui::PruneButtonSpec;
using ui::ResolvedSample;
using ui::Role;
using ui::Selection;
using ui::SlotCellRect;
using ui::TabHit;
using ui::TabHitKind;
using ui::TabRect;
using ui::TabStripLayout;
using ui::TabStripRect;
using ui::TabStripSpec;
using ui::TagTarget;
using ui::ThumbnailKey;
using ui::TooltipBox;
using ui::TooltipSpec;
using ui::applyClick;
using ui::assemblePathList;
using ui::cardNameStrip;
using ui::clampTabScroll;
using ui::columnsForWidth;
using ui::computeBarSlots;
using ui::computeFooterBar;
using ui::computeMenuButton;
using ui::computePruneButton;
using ui::computeSlotRects;
using ui::computeSlotRectsForDrop;
using ui::computeTabRects;
using ui::computeTabStripLayout;
using ui::computeTooltip;
using ui::cueForDropClass;
using ui::cursorForGesture;
using ui::decideCardGesture;
using ui::decideDropClass;
using ui::formatBarsBeats;
using ui::formatSecondsMs;
using ui::hitTestActionBar;
using ui::hitTestFooterBar;
using ui::hitTestMenuButton;
using ui::hitTestPruneButton;
using ui::hitTestSlot;
using ui::hitTestTabStrip;
using ui::kCardNameScrimAlpha;
using ui::kCardStripHeight;
using ui::kCardStripPad;
using ui::menuButtonReserve;
using ui::modeSegmentEnabled;
using ui::navigate;
using ui::needsSurfaceProbe;
using ui::roleColor;
using ui::stripActionPrefix;
using ui::tagButtonEnabled;
using ui::thumbnailKeyString;
using ui::waveformColumnCount;

// core/view
using view::GuidBaseline;
using view::HeaderRect;
using view::SegmentRect;
using view::computeSegmentRects;
using view::hitTestSegment;
using view::isOnManualLane;

// core/capture
using capture::CaptureActionDef;
using capture::TailMode;
using capture::TailSetting;
using capture::adjustManualMs;
using capture::captureActionTable;
using capture::clampManualMs;
using capture::cycleTailMode;
using capture::kManualStepMs;
using capture::normalizeSlashes;
using capture::resolveBankFile;
using capture::tailToggleLabel;

// core/audio
using audio::Envelope;
using audio::computeEnvelope;

// core/model
using model::BankModel;
using model::Sample;

// core/version
using version::actionDisplayPrefix;
using version::appVersion;
using version::channelCommandId;
using version::dockIdent;
using version::dockTitle;

// core/wire
using wire::buildInstrumentDropPreset;

// All color comes from `theme` by role, drawn through the kit. Only pixel layout
// metrics live here, shared by layout/render/input/drag so draw and hit-test can
// never drift.

inline const GridSpec kGrid{/*cellWidth=*/140, /*cellHeight=*/84, /*gap=*/10};

// Footer: [Arrange|Design] toggle, per-mode count, Tail button, Prune button
// (footer_bar left group + prune_button right).
inline constexpr int kFooterHeight = 30;

// Two task-grouped toolbars drawn through action_bar: top = capture + placement,
// bottom = Design-View tag/switch verbs, directly above the footer.
inline constexpr int kTopToolbarHeight = 28;
inline constexpr int kBottomToolbarHeight = 28;

// Hover-delay tooltip timing, driven off the OnTimer poll (no dedicated timer). Kit
// font is proportional, so char width is a generous estimate (pads, never clips).
inline constexpr unsigned int kTooltipDelayMs = 500;
inline constexpr int kTooltipCharPx = 7;
inline constexpr int kTooltipTextH  = 14;

// Client area top to bottom: top toolbar | split body | bottom toolbar | footer.
// 26, not 24: Font::RegionTitle's line box (19px em + Segoe UI's leading) is ~25px, and
// DT_VCENTER clips to the rect.
inline constexpr int kRegionHeaderHeight = 26;
inline constexpr int kTabStripHeight     = 26;
inline constexpr int kSplitDividerHeight = 3;
inline constexpr int kFullHtBtnWidth     = 22;
inline constexpr int kCreateBtnWidth     = 22;

inline const TabStripSpec kTabSpec{/*tabWidth=*/96, /*chevronWidth=*/20};

// Far-right More ("...") overflow-menu button: one source of truth for its
// geometry + the reserve action_bar leaves for it.
inline const MenuButtonSpec kMenuBtnSpec{/*buttonWidth=*/28, /*rightInset=*/6,
                                         /*verticalInset=*/3, /*minLeftInset=*/40};

// Shared by both toolbars' draw and hit-test (identical button shape top and
// bottom). clusterGap is wider than buttonGap so semantic groups read as groups.
inline const ActionBarSpec kBarSpec{/*buttonWidth=*/108, /*buttonGap=*/4, /*clusterGap=*/24,
                                    /*sidePad=*/8, /*verticalInset=*/3};

struct CachedThumbnail {
    Envelope envelope;
    int width = 0;
};

// Which of the two split regions currently owns the selection / receives keyboard
// input. The move/copy source is the focused region's displayed bank.
enum class Region { Pool, Banks };

// What a drag is dropping onto, resolved live under the pointer. BanksRegion fires
// when the pointer is anywhere in the named-banks grid that is NOT on a specific tab
// (tab takes precedence); the resolved bank is always shownBankId.
enum class DropKind { None, PoolRegion, Tab, BanksRegion };

// The hovered interactive element, resolved live in WM_MOUSEMOVE so the kit draws its
// hover state on that element only. SWELL exposes no WM_MOUSELEAVE (confirmed: no hit in
// vendor/WDL/WDL/swell), so hover is cleared by a move that resolves to None rather than a
// leave message; the panel is Windows-only but this stays portable-safe.
// `index` disambiguates within a kind (action-bar button index, tab index); -1 when N/A.
enum class HoverKind {
    None,
    TopBarButton,     // index = flat action index into topBarRows
    BottomBarButton,  // index = flat action index into bottomBarRows
    MoreButton,
    PruneButton,
    FullHtPool,
    FullHtBanks,
    CreateBank,
    Tab,              // index = tab ordinal
    TailButton,
    ModeSegment,      // index = segment ordinal
};

struct Hover {
    HoverKind kind = HoverKind::None;
    int index = -1;

    bool operator==(const Hover& o) const { return kind == o.kind && index == o.index; }
    bool operator!=(const Hover& o) const { return !(*this == o); }
};

// Base rest/hover resolver; Active/Pressed are decided per-element by the caller
// (e.g. an active tab draws Active regardless of hover).
inline InteractionState hoverState(const Hover& hovered, HoverKind kind, int index) {
    return (hovered.kind == kind && hovered.index == index) ? InteractionState::Hover
                                                            : InteractionState::Rest;
}

struct PanelState {
    ReaSamplerSession* session = nullptr;

    HWND hwnd = nullptr;
    bool open = false;

    std::string bankFingerprint;
    std::uint64_t generation = 0;

    std::unordered_map<std::string, CachedThumbnail> cache;

    // One live selection, scoped to `focusedRegion`. Switching regions moves the
    // selection with the focus.
    Selection selection;
    int       selItemCount = 0;
    Region    focusedRegion = Region::Pool;

    // Resolved on WM_MOUSEMOVE; repaint fires only when this changes.
    Hover hovered;

    // Custom hover-delay tooltip. hoverSinceTick resets on every hover change;
    // tooltipShown latches once the delay elapses so the OnTimer poll repaints once.
    unsigned int hoverSinceTick = 0;
    bool tooltipShown = false;

    // Polled on the OnTimer tick (REAPER exposes no transport-change callback). Feeds
    // modeSegmentEnabled (core/ui/footer_bar.h). Cached rather than read per paint AND
    // per click so the pixel the user saw and the click they made cannot disagree
    // within a tick.
    bool modeSwitchBlocked = false;

    BankPanelFullHeight fullHeight = BankPanelFullHeight::Split;

    // The named bank the banks region shows — distinct from the active/capture-target
    // bank. Reconciled each fingerprint pass so it always names a live bank (or empty).
    std::string shownBankId;

    int tabScroll = 0;

    // A drag begins only after the pointer moves past a threshold from a press
    // that landed on a selected grid cell — disambiguates from the multi-select
    // drag (which begins immediately on any grid press).
    bool dragArmed = false;
    bool dragging = false;
    int  dragStartX = 0, dragStartY = 0;
    Region dragSourceRegion = Region::Pool;
    std::string dragSourceBankId;
    std::vector<std::string> dragSampleIds;
    std::string dragPrimaryId;             // the single card grabbed — the reorder/replace subject
    DropKind dropKind = DropKind::None;
    std::string dropBankId;                // destination bank id when dropKind==Tab

    // Resolved each mouse-move by card_drag::decideCardGesture. dragTargetSlot >= 0
    // only for a Reorder/Replace over the source bank's own grid.
    CardGesture cardGesture = CardGesture::None;
    int         dragTargetSlot = -1;

    // NO out-of-client drop-target state is kept here on purpose (core/ui/CLAUDE.md: "the
    // drag-out law is per-move and stateless"). Do not reintroduce a remembered target or a
    // "blocked" latch.

    // Authoritative tail setting lives in ReaSamplerSession, not here; panel reads it for
    // drawing and mutates via footer click / scroll-wheel. bankPanelTailSetting is the
    // capture actions' read seam.

    preview_register_t preview{};
    PCM_source* previewSrc = nullptr;
    bool previewActive = false;
    bool previewInited = false;  // guards double init / deinit

    // Each timer tick diffs the live track+item GUID set against the previous tick to
    // auto-tag new content. GuidBaseline self-arms on first observe() so pre-existing
    // content is never mass-tagged. Project-load re-arm is driven by persist's load
    // signal, not a ReaProject* compare — a recycled address previously mis-tagged tracks.
    GuidBaseline contentBaseline;
    bool         reloadPending = false;  // set by bankPanelNotifyProjectLoaded; drained next detect tick
};

// Defined in panel_window.cpp (the lifecycle owner).
extern PanelState g_panel;

// The grid draws in the bank's persisted SlotMap order (sparse, gap-preserving),
// NOT BankModel insertion order. regionDisplay (panel_layout.cpp) is the single
// place that resolves a region's display.
//
// TWO INDEX SPACES the whole panel must keep straight:
//   * SLOT      — display position 0..maxSlot; gaps are empty, valid drop targets.
//                 What pixels/hit-tests speak.
//   * SELECTION — the dense occupied-ordinal [0, occupied) space the pure
//                 Selection/applyClick/navigate reason in. Selection index i <->
//                 orderedIds[i]. Keyboard nav therefore skips gaps.
// RegionDisplay carries both plus the translation, resolved fresh each call (never
// cached across a mutation, per the reference-invalidation guardrail).
struct RegionDisplay {
    std::vector<std::string>  orderedIds;  // occupied ids in slot order (selection space)
    std::vector<SlotCellRect> slotRects;   // one rect per slot 0..maxSlot, viewport coords
    const Bank*               bank = nullptr;

    // The id occupying `slot`, or "" for an empty slot / out of range.
    std::string idAtSlot(int slot) const {
        return bank ? bank->slots.idAt(slot) : std::string{};
    }
    // The slot a selection ordinal `sel` maps to, or -1. orderedIds[sel] -> its slot.
    int slotForSelection(int sel) const {
        if (sel < 0 || sel >= static_cast<int>(orderedIds.size()) || !bank) return -1;
        return bank->slots.slotOf(orderedIds[static_cast<std::size_t>(sel)]);
    }
    // The selection ordinal for `slot` (index of its occupant in orderedIds), or -1 when
    // the slot is empty. Inverse of slotForSelection.
    int selectionForSlot(int slot) const {
        const std::string id = idAtSlot(slot);
        if (id.empty()) return -1;
        for (std::size_t i = 0; i < orderedIds.size(); ++i)
            if (orderedIds[i] == id) return static_cast<int>(i);
        return -1;
    }
    int occupiedCount() const { return static_cast<int>(orderedIds.size()); }
};

// One action button. Row order IS the flat action index the pure action_bar slots
// carry. Built by panel_layout; consumed by render, input, drag.
struct ActionBarRow {
    std::string suffix;
    std::string shortLabel;
    std::string fullName;
    ActionCluster cluster = ActionCluster::Capture;
    bool enabled = true;  // opposite-mode gate for bottom-bar tag buttons; always true
                          // for the top bar (unconditional triggers)
};

// Modifier state at event time. Alt = the replace modifier.
inline bool ctrlDown() { return (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; }
inline bool shiftDown() { return (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; }
inline bool altDown() { return (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; }

inline void invalidatePanel() {
    if (g_panel.hwnd) InvalidateRect(g_panel.hwnd, nullptr, FALSE);
}

// Cross-seam contract, grouped by owning TU; all plain free functions.

// panel_bank_ops.cpp — book/bank accessors + the bank-CRUD verbs + menus.
BankBook* book();
const BankModel* indexForRegion(Region r);
std::string bankIdForRegion(Region r);
std::vector<const Bank*> namedBanks();
std::string currentProjectDir();
void doCreateBank();
void transferSamples(const std::vector<std::string>& sampleIds,
                     const std::string& srcBankId, const std::string& destBankId,
                     bool copy);
void removeSamples(const std::vector<std::string>& sampleIds,
                   const std::string& srcBankId);
std::vector<std::string> focusedSelectionIds();
std::vector<std::string> resolveDragPathsForOs();
void showTabMenu(int screenX, int screenY, const std::string& bankId);
void showSelectionMenu(int screenX, int screenY);
void showMoreMenu();

// panel_layout.cpp — toolbar/footer/menu rects, row/cluster builders, split geometry,
// region rects, the display bridge. Draw and hit-test both call these so they never drift.
int modeCount();
MenuButtonRect topMenuButtonRect(int w);
ActionBarRect topToolbarActionRect(int w);
RECT panelFooter(int w, int h);
FooterBarLayout footerBarLayoutFor(int w, int h);
ButtonRect pruneButtonRectFor(int w, int h);
bool pointInFooter(int x, int y);
ActionBarRect bottomToolbarRect(int w, int h);
RECT splitBody(int w, int h);
bool poolShown();
bool banksShown();
RECT poolRegionRect(int w, int h);
RECT banksRegionRect(int w, int h);
RECT regionHeaderRect(const RECT& region);
TabStripRect banksTabStripRect(const RECT& region);
RECT regionGridRect(const RECT& region, bool isBanks);
RECT fullHtBtnRect(const RECT& region);
RECT createBtnRect(const RECT& region);
RegionDisplay regionDisplay(const RECT& region, bool isBanks, Region reg);
RegionDisplay focusedDisplay();
bool regionAt(int x, int y, Region& out);
int footerToggleSegmentHit(int x, int y, int w, int h);
int columnsForRegion(Region reg);
std::vector<ActionBarRow> topBarRows();
std::vector<ActionBarRow> bottomBarRows();
std::vector<ActionBarRow> overflowMenuRows();
std::vector<ClusterSpec> actionBarClusters(const std::vector<ActionBarRow>& rows);
int resolveBarCommandId(const ActionBarRow& row);
int toolbarHit(int x, int y, const ActionBarRect& bar, const std::vector<ActionBarRow>& rows);
bool currentTooltip(int w, int h, std::string& textOut, int& ax, int& ay, int& aw, int& ah);

// panel_render.cpp — the full LICE paint (drawn last-to-front into the caller's
// double buffer; BitBlt'd once by paintPanel).
void paintPanel(HWND hwnd, HDC hdc);

// panel_thumbnails.cpp — thumbnail compute + cache, and the bank-change fingerprint
// pass that owns the cache's generation key (bumps generation, clears the cache, and
// reconciles selection/shown-bank on any book mutation).
const Envelope& thumbnailFor(const Sample& sample, int width, const std::string& projectDir);
bool refreshFingerprint();
void reconcileShownBank();

// panel_audition.cpp — the preview engine (hot path: direct call-through, never
// virtual, no added header->TU indirection).
void initPreview();
void deinitPreview();
void stopAudition();
void startAudition(int idx);

// panel_input.cpp — click/wheel/key routing, accelerator, new-content detection,
// and the session-tail read/mutate helpers.
TailSetting currentTail();
// The registered activate action behind a footer mode segment, or 0 if unroutable.
// Shared with panel_render.cpp so paint and click resolve the same id.
int modeActivateCommandId(const std::string& modeId);
void handleClick(int x, int y);
bool handleWheel(int x, int y, int delta);
void registerAccel();
void unregisterAccel();

// panel_drag.cpp — the card-drag/hover state machine (pure mirror: core/ui/card_drag).
// Per-mouse-move work stays plain free-function calls.
void onMouseMove(int x, int y);
void onLBtnUp(int x, int y);
void handleRightClick(int x, int y);
void resetDragState();
void maybeShowTooltip();

}  // namespace reasampler::panel
