// editor_paint_curve.cpp — the velocity->amp curve surfaces: the chrome band's mini
// preview button and the modal popup sheet that hosts the full editor. Band-independent
// (the popup floats over the whole face). Windows-only.

#include "shell/instrument/reasampler_editor.h"

#ifdef _WIN32

#include "core/instrument/ui/curve_popup.h"     // centered curve-popup sheet geometry
#include "shell/instrument/editor_internal.h"   // kit adapters + curveBoxFromRect
#include "shell/instrument/reasampler_processor.h"

namespace reasampler::vst {

using namespace reasampler::ui;              // kit vocabulary
using namespace reasampler::instrument::ui;  // popup geometry

void ReaSamplerEditor::paintCurveButton(LICE_IBitmap* bmp, const Rect& r) {
    if (r.width <= 0 || r.height <= 0) return;
    // A hairline-bordered bg/cell square with the live velocity curve traced in miniature
    // (no node markers at this scale). Hover lifts it; it draws Active (accent-primary
    // border) while its popup is open, and re-renders live as the popup edits the curve.
    const bool hov = isHovered(HoverKind::kCurveButton, -1);
    fillSurface(bmp, toKitBox(r), Role::BgCell,
                hov ? InteractionState::Hover : InteractionState::Rest);
    const KitColor border = curvePopupOpen_ ? roleColor(Role::AccentPrimary)
                                            : roleColor(Role::LineHairline);
    LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1, toLice(border), 1.0f, 0);
    const VelocityCurve& curve = params_.velocityCurve;
    const int inset = 3;
    const VelocityCurve::Box mini{r.x + inset, r.y + inset, r.width - 2 * inset,
                                  r.height - 2 * inset};
    if (mini.width > 1 && mini.height > 1) {
        const LICE_pixel trace = toLice(roleColor(Role::AccentSecondary));
        int prevX = 0, prevY = 0;
        for (int px = 0; px <= mini.width; ++px) {
            const int mx = mini.left + px;
            const double vel = VelocityCurve::pointFromPixel(mini, mx, mini.top).velocity;
            const int my = VelocityCurve::pixelFromPoint(mini, {vel, curve.eval(vel)}).y;
            if (px > 0) LICE_Line(bmp, prevX, prevY, mx, my, trace, 1.0f, 0, true);
            prevX = mx;
            prevY = my;
        }
    }
}

void ReaSamplerEditor::paintCurvePopup(LICE_IBitmap* bmp, int w, int h) {
    // The 0.50-alpha bg/base wash (lighter than Browse's 0.82 — a focused sub-editor; the
    // face stays legible behind it), then the centered sheet.
    LICE_FillRect(bmp, 0, 0, w, h, toLice(roleColor(Role::BgBase)), 0.50f, 0);
    const CurvePopupLayout pl = computeCurvePopup(w, h);
    fillSurface(bmp, toKitBox(pl.sheet), Role::BgPanel, InteractionState::Rest);
    LICE_DrawRect(bmp, pl.sheet.x, pl.sheet.y, pl.sheet.width - 1,
                  pl.sheet.height - 1, toLice(roleColor(Role::LineHairline)), 1.0f, 0);
    kitText(bmp, pl.title, "VELOCITY -> AMP", Font::Micro, Role::TextDim);
    {
        const KitButtonBox box{toKitBox(pl.close)};
        const InteractionState st = isHovered(HoverKind::kPopupClose, -1)
                                        ? InteractionState::Hover
                                        : InteractionState::Rest;
        drawButton(bmp, box, "x", st, /*warn=*/false);
    }
    // The full-size editor: one draw path + the one curveBoxFromRect mapping formula, so
    // trace/handles/drag-off cues cannot drift from the hit-test.
    paintVelocityCurve(bmp, pl.curveBox);
}

void ReaSamplerEditor::paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r) {
    if (r.width <= 0 || r.height <= 0) return;  // defensive (degenerate rect)

    // The bordered box: a panel surface + hairline border, drawn by palette role. No corner
    // caption — the popup sheet's own "VELOCITY -> AMP" title labels this context (the popup
    // is the only host).
    fillSurface(bmp, toKitBox(r), Role::BgPanel, InteractionState::Rest);
    LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1,
                  toLice(roleColor(Role::LineHairline)), 1.0f, 0);

    const VelocityCurve::Box box = curveBoxFromRect(r);
    if (box.width <= 0 || box.height <= 1) return;
    const VelocityCurve& curve = params_.velocityCurve;

    // Trace the monotone spline — ONE eval per x column over the mapping box, in the categorical
    // secondary accent (the same grammar as the envelope trace over the waveform). The x ->
    // velocity and amp -> y mappings both go through the pure module so the trace, the node
    // handles, and the hit-test all share one coordinate system.
    const LICE_pixel line = toLice(roleColor(Role::AccentSecondary));
    int prevX = 0, prevY = 0;
    for (int px = 0; px <= box.width; ++px) {
        const int cx = box.left + px;
        const double vel = VelocityCurve::pointFromPixel(box, cx, box.top).velocity;
        const int cy = VelocityCurve::pixelFromPoint(box, {vel, curve.eval(vel)}).y;
        if (px > 0) LICE_Line(bmp, prevX, prevY, cx, cy, line, 1.0f, 0, true);
        prevX = cx;
        prevY = cy;
    }

    // Draggable node handles (mirror of the envelope overlay's): accent-primary squares lifted
    // to accent-hot when grabbed or hovered, or warn when a drag-off delete is armed (cursor
    // has passed kCurveDragOffMargin outside the box — release will delete the node).
    const LICE_pixel handle = toLice(roleColor(Role::AccentPrimary));
    const LICE_pixel handleHot = toLice(roleColor(Role::AccentHot));
    const LICE_pixel handleWarn = toLice(roleColor(Role::Warn));
    // Drag-off check: during a kCurveNode drag on THIS box, is the live cursor beyond the margin?
    const bool dragOffArmed = (drag_ == DragKind::kCurveNode && dragCurveRect_.x == r.x &&
                               dragCurveRect_.y == r.y) &&
                              (dragCurX_ < r.x - kCurveDragOffMargin ||
                               dragCurX_ > r.right() + kCurveDragOffMargin ||
                               dragCurY_ < r.y - kCurveDragOffMargin ||
                               dragCurY_ > r.bottom() + kCurveDragOffMargin);
    for (std::size_t i = 0; i < curve.points().size(); ++i) {
        const auto np = VelocityCurve::pixelFromPoint(box, curve.points()[i]);
        const bool grabbed = (drag_ == DragKind::kCurveNode &&
                              curvePointIndex_ == static_cast<int>(i));
        const bool hot = grabbed || isHovered(HoverKind::kCurveNode, static_cast<int>(i));
        // A grabbed node in drag-off territory draws warn to signal "release will delete."
        const LICE_pixel col = (grabbed && dragOffArmed) ? handleWarn
                                                         : (hot ? handleHot : handle);
        const int nr = 3;
        LICE_FillRect(bmp, np.x - nr, np.y - nr, 2 * nr, 2 * nr, col, 1.0f, 0);
    }
}

}  // namespace reasampler::vst

#endif  // _WIN32
