// knob_deck.cpp — see knob_deck.h. Pure arithmetic; no LICE/VST3/REAPER includes.

#include "core/instrument/ui/knob_deck.h"

#include <algorithm>

namespace reasampler::instrument::ui {

namespace {

// The knob-row width of a group: cells side by side (no inter-cell gap — the 48px cell
// already carries its own breathing room around the 28px knob), plus the optional row
// toggle after a kDeckToggleGap.
int knobRowWidth(const DeckGroupDesc& g) {
    int w = static_cast<int>(g.cellIds.size()) * kDeckCellW;
    if (g.rowToggle.id >= 0) {
        if (w > 0) w += kDeckToggleGap;
        w += 2 * g.rowToggle.segWidth;
    }
    return w;
}

// The caption-row width: the caption reserve plus the optional caption toggle.
int captionRowWidth(const DeckGroupDesc& g) {
    int w = g.captionWidth;
    if (g.captionToggle.id >= 0) w += kDeckToggleGap + 2 * g.captionToggle.segWidth;
    return w;
}

// Place one group's inner geometry given its box.
DeckGroupLayout layoutGroup(const DeckGroupDesc& g, const Rect& box) {
    DeckGroupLayout out;
    out.id = g.id;
    out.box = box;

    const int captionTop = box.y + kDeckGroupPadY;
    const int innerLeft = box.x + kDeckGroupPadX;
    const int innerRight = box.right() - kDeckGroupPadX;

    // Caption row: text left, compact toggle right-anchored.
    out.caption = Rect::ltrb(innerLeft, captionTop, innerRight, captionTop + kDeckCaptionH);
    if (g.captionToggle.id >= 0) {
        const int segW = g.captionToggle.segWidth;
        const int togTop = captionTop + (kDeckCaptionH - kDeckToggleH) / 2;
        const Rect seg1 = Rect::ltrb(innerRight - segW, togTop, innerRight, togTop + kDeckToggleH);
        const Rect seg0 = Rect::ltrb(seg1.x - segW, togTop, seg1.x, togTop + kDeckToggleH);
        out.captionToggle = DeckToggleLayout{g.captionToggle.id, seg0, seg1};
        // Caption text stops at the toggle: pull the right edge in (XYWH: shrink width).
        out.caption.width = (seg0.x - kDeckToggleGap) - out.caption.x;
    }

    // Knob row: fixed cells left-to-right, then the optional row toggle.
    const int cellTop = captionTop + kDeckCaptionH + kDeckCaptionGap;
    int x = innerLeft;
    for (int id : g.cellIds) {
        DeckCellLayout c;
        c.id = id;
        c.cell = Rect::ltrb(x, cellTop, x + kDeckCellW, cellTop + kDeckCellH);
        const int knobLeft = x + (kDeckCellW - kDeckKnobSize) / 2;
        const int knobTop = cellTop + 4;
        c.knob = Rect::ltrb(knobLeft, knobTop, knobLeft + kDeckKnobSize, knobTop + kDeckKnobSize);
        const int labelTop = knobTop + kDeckKnobSize + 4;
        c.label = Rect::ltrb(c.cell.x, labelTop, c.cell.right(), labelTop + kDeckCellLabelH);
        out.cells.push_back(c);
        x += kDeckCellW;
    }
    if (g.rowToggle.id >= 0) {
        if (!g.cellIds.empty()) x += kDeckToggleGap;
        const int segW = g.rowToggle.segWidth;
        const int togTop = cellTop + (kDeckCellH - kDeckToggleH) / 2;
        const Rect seg0 = Rect::ltrb(x, togTop, x + segW, togTop + kDeckToggleH);
        const Rect seg1 = Rect::ltrb(seg0.right(), togTop, seg0.right() + segW, togTop + kDeckToggleH);
        out.rowToggle = DeckToggleLayout{g.rowToggle.id, seg0, seg1};
    }
    return out;
}

}  // namespace

int deckGroupWidth(const DeckGroupDesc& g) {
    return (std::max)(captionRowWidth(g), knobRowWidth(g)) + 2 * kDeckGroupPadX;
}

int deckRowCount(const std::vector<DeckGroupDesc>& groups, int availWidth) {
    if (groups.empty()) return 0;
    int rows = 1;
    int x = 0;
    for (const DeckGroupDesc& g : groups) {
        const int w = deckGroupWidth(g);
        if (x > 0 && x + kDeckGroupGap + w > availWidth) {
            ++rows;
            x = w;
        } else {
            x += (x > 0 ? kDeckGroupGap : 0) + w;
        }
    }
    return rows;
}

int deckHeight(const std::vector<DeckGroupDesc>& groups, int availWidth) {
    const int rows = deckRowCount(groups, availWidth);
    if (rows == 0) return 0;
    return rows * kDeckGroupH + (rows - 1) * kDeckRowGap;
}

DeckLayout layoutDeck(const std::vector<DeckGroupDesc>& groups, int left, int top,
                      int availWidth) {
    DeckLayout out;
    if (groups.empty()) return out;
    int x = left;
    int y = top;
    bool rowHasGroup = false;
    out.rowCount = 1;
    for (const DeckGroupDesc& g : groups) {
        const int w = deckGroupWidth(g);
        if (rowHasGroup && (x + kDeckGroupGap + w) > (left + availWidth)) {
            // Wrap: whole trailing group onto the next row (mirror of deckRowCount).
            ++out.rowCount;
            x = left;
            y += kDeckGroupH + kDeckRowGap;
            rowHasGroup = false;
        }
        if (rowHasGroup) x += kDeckGroupGap;
        const Rect box = Rect::ltrb(x, y, x + w, y + kDeckGroupH);
        out.groups.push_back(layoutGroup(g, box));
        x = box.right();
        rowHasGroup = true;
    }
    out.height = out.rowCount * kDeckGroupH + (out.rowCount - 1) * kDeckRowGap;
    return out;
}

DeckHit hitTestDeck(const DeckLayout& layout, int x, int y) {
    for (const DeckGroupLayout& g : layout.groups) {
        if (!contains(g.box, x, y)) continue;
        if (g.captionToggle.id >= 0) {
            if (contains(g.captionToggle.seg0, x, y))
                return {DeckHitKind::CaptionToggle, g.captionToggle.id, 0};
            if (contains(g.captionToggle.seg1, x, y))
                return {DeckHitKind::CaptionToggle, g.captionToggle.id, 1};
        }
        if (g.rowToggle.id >= 0) {
            if (contains(g.rowToggle.seg0, x, y))
                return {DeckHitKind::RowToggle, g.rowToggle.id, 0};
            if (contains(g.rowToggle.seg1, x, y))
                return {DeckHitKind::RowToggle, g.rowToggle.id, 1};
        }
        for (const DeckCellLayout& c : g.cells) {
            if (c.id >= 0 && contains(c.cell, x, y)) return {DeckHitKind::Knob, c.id, -1};
        }
        return {};  // inside the box but on fence/padding/blank — a miss (groups never overlap)
    }
    return {};
}

}  // namespace reasampler::instrument::ui
