// knob_deck.h — knob-deck layout + hit-test for the Sample-face knob deck. Engine-free
// like param_slider: cells and toggles carry opaque shell-owned control ids. Mirror of
// action_bar/param_slider; the knob primitive itself (value<->needle-angle, drag) is
// param_slider's — a knob cell here is just a rect the shell composes it into.
//
// The deck is a horizontal run of fenced groups, left->right, each a bordered box with a
// caption row (caption left, the group's compact mode toggle right-anchored) over a knob
// row of fixed cells (knob centered, label band beneath). A group may also place one
// two-segment toggle in the knob row after its cells. Groups that must keep stable
// geometry across a mode flip reserve blank cells (id -1) so a mode flip never reflows
// neighbouring groups.
//
// Wrap is deterministic: groups place left-to-right with kDeckGroupGap between; a group
// that does not fit the remaining width starts a new row (whole groups only, never
// split); the first group of a row always places even if wider than the row.

#pragma once

#include <vector>

#include "core/instrument/ui/editor_geometry.h"  // Rect, contains — the shared geometry idiom

namespace reasampler::instrument::ui {

// Fixed deck metrics, exposed so the shell and tests agree.
inline constexpr int kDeckCellW = 48;         // one knob cell
inline constexpr int kDeckCellH = 58;
inline constexpr int kDeckKnobSize = 28;      // knob diameter inside the cell
inline constexpr int kDeckCellLabelH = 12;    // the Micro label band under the knob
inline constexpr int kDeckCaptionH = 20;      // the group caption row
inline constexpr int kDeckToggleH = 18;       // compact toggle segment height
inline constexpr int kDeckGroupPadX = 6;      // group box horizontal inner padding
inline constexpr int kDeckGroupPadY = 4;      // group box vertical inner padding
inline constexpr int kDeckCaptionGap = 2;     // caption row -> knob row gap
inline constexpr int kDeckToggleGap = 4;      // caption text -> toggle / cells -> row toggle gap
inline constexpr int kDeckGroupGap = 12;      // gap between groups on a row
inline constexpr int kDeckRowGap = 8;         // gap between wrapped deck rows
// One group box: padding + caption + gap + cell row + padding.
inline constexpr int kDeckGroupH =
    kDeckGroupPadY + kDeckCaptionH + kDeckCaptionGap + kDeckCellH + kDeckGroupPadY;

// A two-segment compact toggle (always 2 segments — the Mono/Stereo grammar). id -1 = absent.
struct DeckToggleDesc {
    int id = -1;        // shell control id returned by the hit-test; -1 = no toggle
    int segWidth = 44;  // px per segment
};

// One fenced group, in deck order. `cellIds` are the knob cells left-to-right; an id of -1
// is a reserved blank cell (geometry held, never hit). `captionWidth` is the px the shell
// reserves for the caption text (this module does not measure text).
struct DeckGroupDesc {
    int id = 0;                    // shell group id (opaque here)
    int captionWidth = 60;
    DeckToggleDesc captionToggle;  // right-anchored in the caption row; id -1 = none
    std::vector<int> cellIds;      // knob cells; -1 = blank reserve
    DeckToggleDesc rowToggle;      // in the knob row after the cells; id -1 = none
};

// --- Laid-out geometry ---------------------------------------------------------------

struct DeckToggleLayout {
    int id = -1;
    Rect seg0;  // left segment
    Rect seg1;  // right segment
};

struct DeckCellLayout {
    int id = -1;
    Rect cell;   // the full 48x58 cell
    Rect knob;   // the centered kDeckKnobSize square (the knob circle inscribes it)
    Rect label;  // the 12px label band beneath the knob
};

struct DeckGroupLayout {
    int id = 0;
    Rect box;                       // the fenced group box
    Rect caption;                   // caption text rect (left part of the caption row)
    DeckToggleLayout captionToggle; // id -1 when absent (rects empty)
    std::vector<DeckCellLayout> cells;
    DeckToggleLayout rowToggle;     // id -1 when absent
};

struct DeckLayout {
    std::vector<DeckGroupLayout> groups;
    int rowCount = 0;
    int height = 0;  // rowCount * kDeckGroupH + (rowCount-1) * kDeckRowGap; 0 for no groups
};

// Width of one group box: the wider of its caption row (caption + gap + toggle) and its
// knob row (cells + gap + row toggle), plus horizontal padding.
int deckGroupWidth(const DeckGroupDesc& g);

// Number of deck rows the groups occupy at `availWidth` under the greedy whole-group wrap.
// 0 for an empty list.
int deckRowCount(const std::vector<DeckGroupDesc>& groups, int availWidth);

// Total deck height at `availWidth` (rows * kDeckGroupH + inter-row gaps). The shell
// bottom-anchors a band of exactly this height.
int deckHeight(const std::vector<DeckGroupDesc>& groups, int availWidth);

// Lays the groups out from (left, top) within `availWidth`, wrapping per deckRowCount's
// rule. Every rect is absolute.
DeckLayout layoutDeck(const std::vector<DeckGroupDesc>& groups, int left, int top,
                      int availWidth);

// --- Hit-test --------------------------------------------------------------------------

enum class DeckHitKind { None, Knob, CaptionToggle, RowToggle };

struct DeckHit {
    DeckHitKind kind = DeckHitKind::None;
    int id = -1;       // the control id of the hit element (cell id / toggle id)
    int segment = -1;  // 0/1 for a toggle hit; -1 otherwise
};

// The deck element a point lands on: a knob cell (the whole cell, not just the knob
// circle — the shell anchors the vertical drag wherever the grab lands), a caption-toggle
// segment, or a row-toggle segment. Blank cells (id -1) and everything else miss.
DeckHit hitTestDeck(const DeckLayout& layout, int x, int y);

}  // namespace reasampler::instrument::ui
