// editor_internal.h — shared helpers for the ReaSamplerEditor TU family. Included ONLY by
// the editor's own shell TUs (editor_session / editor_controls / editor_paint_* /
// editor_input_* / editor_platform) — never a public seam. Holds the Rect<->kit adapters,
// small draw primitives (knob face / title band), label helpers, deck group ids, and the
// velocity-curve box derivation. All inline.

#pragma once

#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>

#include "core/instrument/engine/velocity_curve.h"  // VelocityCurve::Box (curveBoxFromRect)
#include "core/instrument/map/sample_map.h"         // SampleChoice / SampleRefs (sampleLabel)
#include "core/instrument/ui/editor_geometry.h"     // Rect (the shared sub-rect type)
#include "core/instrument/ui/keyboard_strip.h"      // noteName (the one note-naming source)

#ifdef _WIN32
#include "wdltypes.h"
#include "lice/lice.h"

#include "core/audio/peaks.h"                      // Envelope (drawEnvelope)
#include "core/instrument/ui/capture_browser.h"    // BrowserLayout / cardThumbnailRect (thumbBins)
#include "core/instrument/ui/param_slider.h"       // KnobGeometry / KnobArc (drawKnobFace)
#include "core/ui/component_geometry.h"            // KitBox / waveformColumnCount
#include "core/ui/theme.h"                         // Role / InteractionState / KitColor / spectralColor
#include "shell/panel/draw_kit.h"                  // the L1 draw kit: fillSurface/text/drawWaveform/toLice
#endif

namespace reasampler::vst {

// Deck group ids (shell-owned; knob_deck treats them opaquely), left-to-right order.
enum DeckGroup {
    kGroupAmpEnv = 0,
    kGroupPitch,
    kGroupPitchEnv,
    kGroupVoice,
    kGroupMaster,
};

// Velocity-curve editor box metrics. The inset keeps node handles + the pick radius
// inside the border so an endpoint at amp 0/1 stays grabbable; drag-off beyond
// box+margin deletes the dragged node.
inline constexpr int kVelCurveInset = 14;
inline constexpr int kCurveDragOffMargin = 24;

// The pure-module mapping Box for a drawn curve rect. Every consumer (paint, hit-test,
// add, drag) derives it through this ONE formula, so drawn nodes and grabs never drift.
inline instrument::engine::VelocityCurve::Box curveBoxFromRect(
    const instrument::ui::Rect& r) {
    return instrument::engine::VelocityCurve::Box{
        r.x + kVelCurveInset, r.y + kVelCurveInset,
        (std::max)(0, r.width - 2 * kVelCurveInset),
        (std::max)(0, r.height - 2 * kVelCurveInset)};
}

// A short MIDI-note label ("C4", "F#3"). The naming itself is the pure strip module's, so
// a browser badge and a strip tooltip can never disagree about what a note is called.
inline std::string noteLabel(int note) {
    return instrument::ui::noteName(note);
}

// A display name for a bank sample id: the snapshotted bank list first, then the
// instance-owned ref's displayName (survives with the extension absent). "?" if neither
// source knows the id.
inline std::string sampleLabel(const std::vector<instrument::map::SampleChoice>& samples,
                               const instrument::map::SampleRefs& refs,
                               const std::string& id) {
    for (const instrument::map::SampleChoice& c : samples) {
        if (c.id == id) return c.displayName.empty() ? c.id : c.displayName;
    }
    for (const instrument::map::SampleRefEntry& e : refs) {
        if (e.sampleId == id && !e.displayName.empty()) return e.displayName;
    }
    return "?";
}

#ifdef _WIN32

// The editor's own sub-rect type is `Rect` (editor_geometry); the kit draws against
// `KitBox` (component_geometry). This is the single boundary that bridges them so every
// draw routes through the shared kit (theme roles + draw_kit).
inline ui::KitBox toKitBox(const instrument::ui::Rect& r) {
    return ui::KitBox{r.x, r.y, r.width, r.height};
}

// Kit text in a palette ROLE (the common case). Left/Right/Center via Align.
inline void kitText(LICE_IBitmap* bmp, const instrument::ui::Rect& r, const char* s,
                    Font font, ui::Role role, Align align = Align::Left) {
    text(bmp, toKitBox(r), s, font, role, align);
}

inline void kitTextCentered(LICE_IBitmap* bmp, const instrument::ui::Rect& r,
                            const char* s, Font font, ui::Role role) {
    text(bmp, toKitBox(r), s, font, role, Align::Center);
}

// Draw a peak envelope in `r` through the kit's shared waveform primitive.
inline void drawEnvelope(LICE_IBitmap* bmp, const instrument::ui::Rect& r,
                         const audio::Envelope& env) {
    drawWaveform(bmp, toKitBox(r), env);
}

// The bin count a card's thumbnail is computed at: one bin per drawn pixel column (the
// gap-free render comes from peaks::columnMinMax's exact partition).
inline int thumbBins(const instrument::ui::BrowserLayout& layout) {
    return (std::max)(1, kWaveformOversample *
                             ui::waveformColumnCount(toKitBox(
                                 instrument::ui::cardThumbnailRect(layout, 0))));
}

// Draws the title band with the live readout. The Browse modal draws its own back button in
// place of the nav.
inline void drawTitleBand(LICE_IBitmap* bmp, const instrument::ui::Rect& title,
                          const std::string& readout) {
    fillSurface(bmp, toKitBox(title), ui::Role::BgPanel, ui::InteractionState::Rest);
    instrument::ui::Rect titleText =
        instrument::ui::Rect::ltrb(title.x + 8, title.y, title.right() - 8, title.bottom());
    kitText(bmp, titleText, readout.c_str(), Font::Title, ui::Role::TextPrimary);
}

// Draws one radial knob face: param_slider owns the value<->angle map; this turns it into
// LICE calls. LICE takes radians, and drawing the 7->5 o'clock sweep through the top needs
// a continuous angle span, so degrees convert as (deg - 360) * pi/180, mapping 210..510
// onto -150..+150 degrees.
inline void drawKnobFace(LICE_IBitmap* bmp, const instrument::ui::Rect& knobRect,
                         double value01, ui::InteractionState st) {
    using instrument::ui::KnobArc;
    using instrument::ui::KnobGeometry;
    using instrument::ui::KnobPoint;
    const KnobGeometry kg = instrument::ui::computeKnob(knobRect);
    if (kg.radius <= 1.0) return;
    constexpr double kDegToRad = 3.14159265358979323846 / 180.0;
    const KnobArc arc{};  // the default 7->5 o'clock sweep
    const float cx = static_cast<float>(kg.centerX);
    const float cy = static_cast<float>(kg.centerY);
    const float rOuter = static_cast<float>(kg.radius) - 0.5f;
    const bool disabled = (st == ui::InteractionState::Disabled);
    const bool hot = (st == ui::InteractionState::Dragging || st == ui::InteractionState::Hover);

    LICE_FillCircle(bmp, cx, cy, rOuter - 1.f, toLice(ui::roleColorState(ui::Role::BgCell, st)),
                    1.0f, 0, true);
    // Track: the full sweep as a hairline arc (the dead 60-degree arc at the bottom stays bare).
    const float a0 = static_cast<float>((arc.startDeg - 360.0) * kDegToRad);
    const float a1 = static_cast<float>(
        (arc.startDeg + instrument::ui::knobSweepDeg(arc) - 360.0) * kDegToRad);
    LICE_Arc(bmp, cx, cy, rOuter, a0, a1, toLice(ui::roleColor(ui::Role::LineHairline)), 1.0f, 0,
             true);
    const double v = value01 < 0.0 ? 0.0 : (value01 > 1.0 ? 1.0 : value01);
    if (v > 0.0) {
        const float av = static_cast<float>(
            (arc.startDeg + v * instrument::ui::knobSweepDeg(arc) - 360.0) * kDegToRad);
        const ui::Role valueRole = disabled ? ui::Role::TextDim
                                            : (hot ? ui::Role::AccentHot : ui::Role::AccentPrimary);
        LICE_Arc(bmp, cx, cy, rOuter, a0, av, toLice(ui::roleColor(valueRole)), 1.0f, 0, true);
    }
    // Needle: from ~35% radius out to the rim at the value's angle.
    const KnobPoint tip = instrument::ui::knobNeedlePoint(kg, arc, v);
    const float ix = cx + static_cast<float>((tip.x - kg.centerX) * 0.35);
    const float iy = cy + static_cast<float>((tip.y - kg.centerY) * 0.35);
    const ui::Role needleRole = disabled ? ui::Role::TextDim : ui::Role::TextPrimary;
    LICE_Line(bmp, static_cast<int>(ix + 0.5f), static_cast<int>(iy + 0.5f),
              static_cast<int>(tip.x + 0.5f), static_cast<int>(tip.y + 0.5f),
              toLice(ui::roleColor(needleRole)), 1.0f, 0, true);
}

#endif  // _WIN32

}  // namespace reasampler::vst
