// editor_input_chrome.cpp — the CHROME band's input: the Browse nav, the preview trigger,
// the preview-velocity knob grab, the loop enable, the channel toggle, and the piano strip's
// root grab plus its live drag. Windows-only.

#include "shell/instrument/reasampler_editor.h"

#ifdef _WIN32

#include "core/instrument/ui/keyboard_strip.h"  // keyAtPoint / resolveDragNote (root key)
#include "core/instrument/ui/knob_deck.h"       // inKnobFace (the shared reset target rule)
#include "shell/instrument/editor_internal.h"
#include "shell/instrument/reasampler_processor.h"

namespace reasampler::vst {

using namespace reasampler::ui;
using namespace reasampler::instrument::ui;

bool ReaSamplerEditor::mouseDownChrome(const FaceLayout& fl, int x, int y) {
    const ChromeRects& cr = fl.chrome;

    if (contains(cr.navBrowse, x, y)) {
        // Open the Browse modal; seed its pending pick from the loaded id so the current
        // capture reads as pre-selected.
        browsePendingId_ = selectedId_;
        lastBrowseClickCard_ = -1;
        view_ = View::kBrowse;
        invalidate();
        return true;
    }
    if (selectedId_.empty()) return false;  // empty state — nav only

    // Preview-trigger button: fire the loaded capture at its root through the voice engine
    // (momentary — note-on on press, note-off on release).
    if (contains(cr.preview, x, y)) {
        const int note = effectiveRoot();
        if (previewingNote_ >= 0) processor_->previewNoteOff(previewingNote_);
        previewingNote_ = note;
        processor_->previewNoteOn(note);
        invalidate();
        return true;
    }
    // Resample bake. Arm only: onSyncTimer runs it a tick later, off this handler's stack
    // and with no mouse capture held (instrument_bake.h says why that matters). A click
    // while the extension is absent is swallowed — the button already paints Disabled.
    if (contains(cr.bake, x, y)) {
        if (bakeAvailable_) bakePending_ = true;
        invalidate();
        return true;
    }
    // Bake Hold: the same grab-anchored knob drag, and inert on exactly the ticks it is not
    // drawn — the cell's width is reserved either way (sample_chrome.h says why).
    if (bakeHoldNeeded_ && contains(cr.holdCell, x, y)) {
        drag_ = DragKind::kDeckKnob;
        dragParamId_ = kBakeHoldKnobId;
        dragInnerCellId_ = -1;
        dragKnobStartValue_ = bakeHoldNorm();
        dragStartParams_ = params_;
        dragStartX_ = x;
        dragStartY_ = y;
        invalidate();
        return true;
    }
    // Radial preview-velocity knob: grab-anchored vertical drag — the grab itself never
    // jumps the value; the delta from the grab point maps via knobDragValue.
    if (contains(cr.velCell, x, y)) {
        drag_ = DragKind::kDeckKnob;
        dragParamId_ = -2;  // sentinel: the preview velocity knob (a processor param)
        dragInnerCellId_ = -1;  // peer of mouseDownDeck's own grab-time init, for symmetry
        dragKnobStartValue_ = previewVelocity01();
        dragStartX_ = x;
        dragStartY_ = y;
        invalidate();
        return true;
    }
    // The loop enable. Inert (not hidden) outside Gate: that refusal comes from the engine and
    // no click can talk it out of it — unlike the user's own off, which the marks themselves
    // still offer to reverse.
    if (loopControlsLive()) {
        if (contains(cr.loopOff, x, y)) {
            setLoopEnabled(false);
            invalidate();
            return true;
        }
        if (contains(cr.loopOn, x, y)) {
            setLoopEnabled(true);
            invalidate();
            return true;
        }
    }
    if (contains(cr.chanMono, x, y)) {
        channelMode_ = ChannelMode::Mono;
        processor_->setChannelMode(ChannelMode::Mono);
        invalidate();
        return true;
    }
    if (contains(cr.chanStereo, x, y)) {
        channelMode_ = ChannelMode::Stereo;
        processor_->setChannelMode(ChannelMode::Stereo);
        invalidate();
        return true;
    }

    // The piano strip: clicking a key sets the root, and holding tracks the pointer. The
    // click itself lands below as the first (unmoved) drag resolve.
    if (!cr.rootStrip.empty()) {
        const StripLayout sl = layoutStrip(cr.rootStrip.width, cr.rootStrip.height);
        if (keyAtPoint(sl, x - cr.rootStrip.x, y - cr.rootStrip.y) >= 0) {
            drag_ = DragKind::kRootMarker;
            dragStartParams_ = params_;
            onMouseMove(x, y);
            return true;
        }
    }
    // A click on the strip row's background is consumed so it can't fall through to a
    // band the user cannot see under the chrome.
    return contains(cr.controls, x, y);
}

bool ReaSamplerEditor::doubleClickChrome(const FaceLayout& fl, int x, int y) {
    // The preview-velocity dial answers the same reset gesture the deck's knobs do — it is a
    // radial knob drawn by the same primitive, so it resolves through the same target rule
    // (inKnobFace), against the drawn circle rather than the cell's label band.
    if (selectedId_.empty() || !processor_) return false;
    if (bakeHoldNeeded_ && inKnobFace(fl.chrome.holdKnob, x, y)) {
        // The default is READ off a default-constructed parameter set, so there is no second
        // table of defaults to drift from the codec's own lift. A reset that changes nothing
        // commits nothing — the same rule the release path applies.
        const instrument::note::Division reset = InstrumentParams{}.bakeHold;
        if (params_.bakeHold != reset) {
            params_.bakeHold = reset;
            commitAndReload();
        }
        invalidate();
        return true;
    }
    if (!inKnobFace(fl.chrome.velKnob, x, y)) return false;
    processor_->setPreviewVelocity(kPreviewVelocityDefault);
    invalidate();
    return true;
}

void ReaSamplerEditor::dragChrome(const FaceLayout& fl, int x, int y) {
    const Rect& stripArea = fl.chrome.rootStrip;
    if (stripArea.empty()) return;
    // Absolute tracking, not a pixel delta: with black keys overlaying whites there is no
    // one pixels-per-semitone rate a delta could use.
    const StripLayout sl = layoutStrip(stripArea.width, stripArea.height);
    const int note = resolveDragNote(sl, x - stripArea.x, y - stripArea.y);
    if (note < 0) return;
    params_.rootOverride = note;
    invalidate();  // live feedback; the commit lands on WM_LBUTTONUP
}

HoverTarget ReaSamplerEditor::hoverChrome(const FaceLayout& fl, int x,
                                          int y) const {
    const ChromeRects& cr = fl.chrome;
    if (contains(cr.navBrowse, x, y)) return {HoverKind::kNavBrowse, -1};
    if (selectedId_.empty()) return {};  // empty state — no interactive surfaces beyond nav
    if (bakeHoldNeeded_ && contains(cr.holdCell, x, y)) return {HoverKind::kHoldKnob, -1};
    if (contains(cr.bake, x, y)) return {HoverKind::kBake, -1};
    if (contains(cr.preview, x, y)) return {HoverKind::kPreview, -1};
    if (contains(cr.velCell, x, y)) return {HoverKind::kVelKnob, -1};
    if (loopControlsLive()) {
        if (contains(cr.loopOff, x, y)) return {HoverKind::kLoopOff, -1};
        if (contains(cr.loopOn, x, y)) return {HoverKind::kLoopOn, -1};
    }
    if (contains(cr.chanMono, x, y)) return {HoverKind::kChanMono, -1};
    if (contains(cr.chanStereo, x, y)) return {HoverKind::kChanStereo, -1};
    if (!cr.rootStrip.empty()) {
        const StripLayout sl = layoutStrip(cr.rootStrip.width, cr.rootStrip.height);
        const int note = keyAtPoint(sl, x - cr.rootStrip.x, y - cr.rootStrip.y);
        if (note >= 0) return {HoverKind::kStripKey, note};
    }
    return {};
}

}  // namespace reasampler::vst

#endif  // _WIN32
