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

#include "tab_strip.h"

#include <cstddef>

namespace reasampler {

TabStripLayout computeTabStripLayout(const TabStripRect& strip, int tabCount,
                                     const TabStripSpec& spec, int scrollOffset) {
    (void)scrollOffset;  // layout depends on geometry only, not the current offset
    TabStripLayout out;
    if (tabCount <= 0 || strip.width <= 0) {
        out.trackX = strip.x;
        out.trackWidth = strip.width > 0 ? strip.width : 0;
        return out;  // nothing to lay out: track == strip, no overflow, no chevrons
    }

    const int totalTabsWidth = tabCount * spec.tabWidth;
    if (totalTabsWidth <= strip.width) {
        // Everything fits: the whole strip is the track; no chevrons, no scroll.
        out.overflow = false;
        out.trackX = strip.x;
        out.trackWidth = strip.width;
        out.maxScroll = 0;
        return out;
    }

    // Overflow: reserve a chevron band at each end; the tabs live between them.
    out.overflow = true;
    out.leftChevron = true;
    out.rightChevron = true;
    out.trackX = strip.x + spec.chevronWidth;
    out.trackWidth = strip.width - 2 * spec.chevronWidth;
    if (out.trackWidth < 0) out.trackWidth = 0;
    // The tab run exceeds the track by this many pixels; the strip may scroll exactly
    // that far so the last tab's right edge reaches the track's right edge, no more.
    out.maxScroll = totalTabsWidth - out.trackWidth;
    if (out.maxScroll < 0) out.maxScroll = 0;
    return out;
}

int clampTabScroll(int desiredOffset, const TabStripLayout& layout) {
    if (desiredOffset < 0) return 0;
    if (desiredOffset > layout.maxScroll) return layout.maxScroll;
    return desiredOffset;
}

std::vector<TabRect> computeTabRects(const TabStripRect& strip, int tabCount,
                                     const TabStripSpec& spec, int scrollOffset) {
    std::vector<TabRect> rects;
    if (tabCount <= 0 || strip.width <= 0) return rects;

    const TabStripLayout layout =
        computeTabStripLayout(strip, tabCount, spec, scrollOffset);
    const int offset = layout.overflow ? clampTabScroll(scrollOffset, layout) : 0;
    const int trackLeft = layout.trackX;
    const int trackRight = layout.trackX + layout.trackWidth;

    rects.reserve(static_cast<std::size_t>(tabCount));
    for (int i = 0; i < tabCount; ++i) {
        const int rawLeft = trackLeft + i * spec.tabWidth - offset;
        const int rawRight = rawLeft + spec.tabWidth;
        // Clip to the track: a partially-scrolled tab must not draw under a chevron
        // or spill past the track. A tab whose clipped extent is empty is omitted.
        int left = rawLeft < trackLeft ? trackLeft : rawLeft;
        int right = rawRight > trackRight ? trackRight : rawRight;
        if (right <= left) continue;  // fully scrolled out of view either side
        TabRect r;
        r.index = i;
        r.x = left;
        r.y = strip.y;
        r.width = right - left;
        r.height = strip.height;
        rects.push_back(r);
    }
    return rects;
}

TabHit hitTestTabStrip(int px, int py, const TabStripRect& strip, int tabCount,
                       const TabStripSpec& spec, int scrollOffset) {
    TabHit miss;  // {None, -1}
    if (tabCount <= 0 || strip.width <= 0 || strip.height <= 0) return miss;

    // Reject anything outside the strip band first (half-open bounds).
    if (px < strip.x || px >= strip.x + strip.width ||
        py < strip.y || py >= strip.y + strip.height)
        return miss;

    const TabStripLayout layout =
        computeTabStripLayout(strip, tabCount, spec, scrollOffset);

    // Chevrons take precedence at the strip ends: a click in a reserved chevron band
    // is a scroll, never a tab (the tab track excludes those bands).
    if (layout.overflow) {
        if (px < strip.x + spec.chevronWidth)
            return TabHit{TabHitKind::ScrollLeft, -1};
        if (px >= strip.x + strip.width - spec.chevronWidth)
            return TabHit{TabHitKind::ScrollRight, -1};
    }

    // Inside the track: find the visible tab whose clipped rect contains px. Reuse
    // computeTabRects so the hit matches exactly what was drawn (clipping included).
    const std::vector<TabRect> rects =
        computeTabRects(strip, tabCount, spec, scrollOffset);
    for (const TabRect& r : rects) {
        if (px >= r.x && px < r.x + r.width) return TabHit{TabHitKind::Tab, r.index};
    }
    return miss;  // track dead space (no tab under the point)
}

} // namespace reasampler
