// action_buttons — pure implementation. See action_buttons.h. NO REAPER / SWELL / vendor.

#include "action_buttons.h"

#include <cstddef>

namespace reasampler {

namespace {

// The left edge of button i when `count` buttons share a strip of the given x-origin and
// width. Boundary i is x + (i * width) / count, so button i spans [edge(i), edge(i+1)).
// Every boundary derives from the same formula, so consecutive buttons share an exact
// edge (no gap, no overlap) and edge(count) == x + width precisely. count assumed >= 1.
int buttonEdge(int x, int width, int i, int count) {
    return x + (i * width) / count;
}

// True for an ASCII space or tab (the whitespace the SDK binding string might carry).
bool isBlankChar(char c) { return c == ' ' || c == '\t'; }

// The trimmed [first, last) view of `s` with leading/trailing blanks removed. Returns
// an empty range when `s` is all blanks.
std::string trimBlanks(const std::string& s) {
    std::size_t b = 0;
    std::size_t e = s.size();
    while (b < e && isBlankChar(s[b])) ++b;
    while (e > b && isBlankChar(s[e - 1])) --e;
    return s.substr(b, e - b);
}

} // namespace

ButtonFit computeButtonFit(const ButtonStripRect& strip, int buttonCount,
                           int minButtonWidth) {
    ButtonFit fit;
    if (buttonCount <= 0 || strip.width <= 0 || minButtonWidth <= 0) return fit;

    int fits = strip.width / minButtonWidth;   // how many min-width buttons the strip holds
    if (fits > buttonCount) fits = buttonCount;
    if (fits < 0) fits = 0;

    fit.visibleCount = fits;
    fit.hiddenCount  = buttonCount - fits;
    return fit;
}

std::vector<ActionButtonRect> computeButtonRects(const ButtonStripRect& strip, int buttonCount,
                                                 int minButtonWidth) {
    std::vector<ActionButtonRect> rects;
    const ButtonFit fit = computeButtonFit(strip, buttonCount, minButtonWidth);
    const int n = fit.visibleCount;
    if (n <= 0) return rects;

    rects.reserve(static_cast<std::size_t>(n));
    for (int i = 0; i < n; ++i) {
        const int left  = buttonEdge(strip.x, strip.width, i, n);
        const int right = buttonEdge(strip.x, strip.width, i + 1, n);
        ActionButtonRect r;
        r.index  = i;
        r.x      = left;
        r.y      = strip.y;
        r.width  = right - left;  // absorbs rounding; visible buttons abut and fill the strip
        r.height = strip.height;
        rects.push_back(r);
    }
    return rects;
}

int hitTestButton(int px, int py, const ButtonStripRect& strip, int buttonCount,
                  int minButtonWidth) {
    if (strip.height <= 0) return -1;
    // Reject anything outside the strip band first (half-open bounds match the rects).
    if (px < strip.x || px >= strip.x + strip.width ||
        py < strip.y || py >= strip.y + strip.height)
        return -1;

    const ButtonFit fit = computeButtonFit(strip, buttonCount, minButtonWidth);
    const int n = fit.visibleCount;
    if (n <= 0) return -1;

    // Inside the band: find the visible button whose [edge(i), edge(i+1)) contains px.
    // A point past the last visible button's right edge (narrow-panel overflow dead-zone)
    // falls through to -1.
    for (int i = 0; i < n; ++i) {
        const int left  = buttonEdge(strip.x, strip.width, i, n);
        const int right = buttonEdge(strip.x, strip.width, i + 1, n);
        if (px >= left && px < right) return i;
    }
    return -1;
}

std::string formatButtonLabel(const std::string& name, const std::string& rawBinding) {
    const std::string binding = trimBlanks(rawBinding);
    if (binding.empty()) {
        return name + " (" + kUnboundMarker + ")";
    }

    std::string shown = binding;
    if (static_cast<int>(shown.size()) > kMaxBindingChars) {
        // Keep the leading portion and mark the truncation with a single-width "~".
        shown = shown.substr(0, static_cast<std::size_t>(kMaxBindingChars - 1)) + "~";
    }
    return name + "  " + shown;
}

} // namespace reasampler
