// editor_geometry.cpp — see editor_geometry.h. Pure math; no host types.

#include "editor_geometry.h"

#include <algorithm>

namespace reasampler::vst {

namespace {

// Spike editor layout constants. These are the editor's fixed metrics; the real
// editor (S4/S5) will parameterize as its content demands.
constexpr int kTitleBarHeight = 28;
constexpr int kButtonMargin = 10;
constexpr int kButtonWidth = 120;
constexpr int kButtonHeight = 24;

}  // namespace

bool contains(const Rect& r, int x, int y) {
    if (r.width() <= 0 || r.height() <= 0) return false;
    return x >= r.left && x < r.right && y >= r.top && y < r.bottom;
}

EditorLayout layoutEditor(int w, int h) {
    // Clamp the surface to non-negative extents so a degenerate view can't produce
    // inverted rects.
    const int cw = std::max(0, w);
    const int ch = std::max(0, h);

    EditorLayout out;

    // Title bar spans the top, clamped so it never exceeds the client height.
    const int titleH = std::min(kTitleBarHeight, ch);
    out.titleBar = Rect{0, 0, cw, titleH};

    // Canvas is everything below the title bar.
    out.canvas = Rect{0, titleH, cw, ch};

    // Button sits at the top-left of the canvas, inset by a margin, and is clamped to
    // fit inside the canvas so it never overhangs on a small view.
    const int bx = out.canvas.left + kButtonMargin;
    const int by = out.canvas.top + kButtonMargin;
    const int bRight = std::min(bx + kButtonWidth, out.canvas.right);
    const int bBottom = std::min(by + kButtonHeight, out.canvas.bottom);
    out.button = Rect{bx, by, std::max(bx, bRight), std::max(by, bBottom)};

    return out;
}

HitTarget hitTest(const EditorLayout& layout, int x, int y) {
    if (contains(layout.button, x, y)) return HitTarget::kButton;
    return HitTarget::kNone;
}

Rect sampleRowRect(const EditorLayout& layout, int index) {
    if (index < 0) return Rect{};
    const int top = layout.canvas.top + index * kSampleRowHeight;
    return Rect{layout.canvas.left, top, layout.canvas.right, top + kSampleRowHeight};
}

int sampleRowHitTest(const EditorLayout& layout, int rowCount, int x, int y) {
    if (rowCount <= 0) return -1;
    // Must be within the canvas horizontally and at/below its top.
    if (x < layout.canvas.left || x >= layout.canvas.right) return -1;
    if (y < layout.canvas.top) return -1;
    // Clip at the canvas bottom: clicks in the canvas's dead-zone below the last
    // visible row agree with sampleRowRect, which does not clamp rows to canvas.bottom.
    if (y >= layout.canvas.bottom) return -1;
    const int index = (y - layout.canvas.top) / kSampleRowHeight;
    if (index < 0 || index >= rowCount) return -1;
    // Guard the bottom edge: a click below the last row's bottom is outside.
    const Rect r = sampleRowRect(layout, index);
    if (y >= r.bottom) return -1;
    return index;
}

// --- Keymap editor -----------------------------------------------------------

KeymapEditorLayout layoutKeymapEditor(int w, int h) {
    KeymapEditorLayout out;
    out.base = layoutEditor(w, h);
    const Rect& canvas = out.base.canvas;

    // Split the canvas vertically: the left column is the bank-sample list, the right
    // column (1/kZonePanelFraction of the width) is the zone panel. Guard tiny widths so
    // the split point never crosses the canvas edges.
    const int canvasW = std::max(0, canvas.width());
    const int splitW = canvasW / kZonePanelFraction;          // width of the zone panel
    const int splitX = std::max(canvas.left, canvas.right - splitW);

    out.sampleList = Rect{canvas.left, canvas.top, splitX, canvas.bottom};
    out.zonePanel = Rect{splitX, canvas.top, canvas.right, canvas.bottom};

    // "Add Zone" button spans the top of the zone panel, clamped to its height.
    const int addH = std::min(kAddZoneHeight, std::max(0, out.zonePanel.height()));
    out.addZoneButton =
        Rect{out.zonePanel.left, out.zonePanel.top, out.zonePanel.right,
             out.zonePanel.top + addH};

    // Zone rows stack below the button.
    out.zoneRowArea = Rect{out.zonePanel.left, out.addZoneButton.bottom,
                           out.zonePanel.right, out.zonePanel.bottom};
    return out;
}

Rect keymapSampleRowRect(const KeymapEditorLayout& layout, int index) {
    if (index < 0) return Rect{};
    const int top = layout.sampleList.top + index * kSampleRowHeight;
    return Rect{layout.sampleList.left, top, layout.sampleList.right,
                top + kSampleRowHeight};
}

int keymapSampleRowHitTest(const KeymapEditorLayout& layout, int rowCount, int x, int y) {
    if (rowCount <= 0) return -1;
    const Rect& list = layout.sampleList;
    if (x < list.left || x >= list.right) return -1;
    if (y < list.top || y >= list.bottom) return -1;
    const int index = (y - list.top) / kSampleRowHeight;
    if (index < 0 || index >= rowCount) return -1;
    const Rect r = keymapSampleRowRect(layout, index);
    if (y >= r.bottom) return -1;
    return index;
}

Rect zoneRowRect(const KeymapEditorLayout& layout, int index) {
    if (index < 0) return Rect{};
    const int top = layout.zoneRowArea.top + index * kZoneRowHeight;
    return Rect{layout.zoneRowArea.left, top, layout.zoneRowArea.right,
                top + kZoneRowHeight};
}

ZoneHit zoneHitTest(const KeymapEditorLayout& layout, int zoneCount, int x, int y) {
    if (zoneCount <= 0) return ZoneHit{};
    const Rect& area = layout.zoneRowArea;
    if (x < area.left || x >= area.right) return ZoneHit{};
    if (y < area.top || y >= area.bottom) return ZoneHit{};
    const int index = (y - area.top) / kZoneRowHeight;
    if (index < 0 || index >= zoneCount) return ZoneHit{};
    const Rect row = zoneRowRect(layout, index);
    if (y >= row.bottom) return ZoneHit{};

    // Seven mini-buttons pinned to the right edge, right-to-left:
    //   delete, root+, root-, high+, high-, low+, low-
    // Each is kZoneCtrlWidth wide. A click left of the leftmost is the label ("select").
    // The fields laid out LEFT-TO-RIGHT in slot order 0..6.
    const ZoneField fields[7] = {
        ZoneField::kLowDown, ZoneField::kLowUp,  ZoneField::kHighDown,
        ZoneField::kHighUp,  ZoneField::kRootDown, ZoneField::kRootUp,
        ZoneField::kDelete,
    };
    const int slots = 7;
    const int ctrlBlockLeft = row.right - slots * kZoneCtrlWidth;
    if (x < ctrlBlockLeft) return ZoneHit{index, ZoneField::kZoneNone};  // label -> select
    const int slot = (x - ctrlBlockLeft) / kZoneCtrlWidth;
    if (slot < 0 || slot >= slots) return ZoneHit{index, ZoneField::kZoneNone};
    return ZoneHit{index, fields[slot]};
}

bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y) {
    return contains(layout.addZoneButton, x, y);
}

}  // namespace reasampler::vst
