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

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

#include <algorithm>

namespace reasampler::instrument::ui {

namespace {

// Pitch class of each natural, and the count of naturals strictly below each pitch class.
constexpr int kNaturalPitchClass[7] = {0, 2, 4, 5, 7, 9, 11};
constexpr int kNaturalsBelowPc[12] = {0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6};

int clampNote(int n) {
    if (n < 0) return 0;
    if (n > kStripKeyCount - 1) return kStripKeyCount - 1;
    return n;
}

// The MIDI note of white key `index` (0..74).
int whiteNoteAt(int index) {
    const int i = index < 0 ? 0 : (index > kStripWhiteKeyCount - 1 ? kStripWhiteKeyCount - 1
                                                                   : index);
    return clampNote((i / 7) * 12 + kNaturalPitchClass[i % 7]);
}

// The black key straddling white-key boundary `b`, or -1 where the scale has none (E-F and
// B-C are adjacent naturals).
int blackNoteAtBoundary(int b) {
    if (b <= 0 || b >= kStripWhiteKeyCount) return -1;
    const int below = whiteNoteAt(b) - 1;
    return (below >= 0 && !isNaturalKey(below)) ? below : -1;
}

}  // namespace

StripLayout layoutStrip(int w, int h) {
    const int cw = std::max(0, w);
    const int ch = std::max(0, h);
    StripLayout out;
    out.band = Rect::ltrb(0, 0, cw, ch);
    if (ch <= 0) return out;

    out.whiteWidth = cw / kStripWhiteKeyCount;
    if (out.whiteWidth <= 0) return out;  // narrower than one pixel per white key

    const int keysW = out.whiteWidth * kStripWhiteKeyCount;
    const int left = (cw - keysW) / 2;  // residue split evenly into the two end margins
    out.keys = Rect::ltrb(left, 0, left + keysW, ch);
    out.blackWidth = std::max(1, (out.whiteWidth * 3) / 5);
    out.blackHeight = std::max(1, (ch * 3) / 5);
    return out;
}

bool isNaturalKey(int note) {
    const int n = clampNote(note);
    static constexpr bool kNatural[12] = {
        true,   // 0  C
        false,  // 1  C#
        true,   // 2  D
        false,  // 3  D#
        true,   // 4  E
        true,   // 5  F
        false,  // 6  F#
        true,   // 7  G
        false,  // 8  G#
        true,   // 9  A
        false,  // 10 A#
        true,   // 11 B
    };
    return kNatural[n % 12];
}

int whiteIndexOf(int note) {
    const int n = clampNote(note);
    return (n / 12) * 7 + kNaturalsBelowPc[n % 12];
}

Rect keyRect(const StripLayout& layout, int note) {
    if (layout.keys.empty()) return Rect{};
    const int n = clampNote(note);
    const int wi = whiteIndexOf(n);
    if (isNaturalKey(n)) {
        const int left = layout.keys.x + wi * layout.whiteWidth;
        return Rect::ltrb(left, layout.keys.y, left + layout.whiteWidth, layout.keys.bottom());
    }
    const int centre = layout.keys.x + wi * layout.whiteWidth;
    const int left = centre - layout.blackWidth / 2;
    return Rect::ltrb(left, layout.keys.y, left + layout.blackWidth,
                      layout.keys.y + layout.blackHeight);
}

Rect rootMarkerRect(const StripLayout& layout, int rootNote) {
    return keyRect(layout, rootNote);
}

int keyAtPoint(const StripLayout& layout, int x, int y) {
    if (!contains(layout.keys, x, y)) return -1;
    const int offset = x - layout.keys.x;
    const int wi = std::min(offset / layout.whiteWidth, kStripWhiteKeyCount - 1);
    if (y < layout.keys.y + layout.blackHeight) {
        // Only the two boundaries flanking this white key can carry an overlapping black.
        const int candidates[2] = {wi, wi + 1};
        for (const int b : candidates) {
            const int note = blackNoteAtBoundary(b);
            if (note >= 0 && contains(keyRect(layout, note), x, y)) return note;
        }
    }
    return whiteNoteAt(wi);
}

int resolveDragNote(const StripLayout& layout, int x, int y) {
    if (layout.keys.empty()) return -1;
    const int cx = std::clamp(x, layout.keys.x, layout.keys.right() - 1);
    const int cy = std::clamp(y, layout.keys.y, layout.keys.bottom() - 1);
    return keyAtPoint(layout, cx, cy);
}

std::string noteName(int note) {
    static constexpr const char* kNames[12] = {"C",  "C#", "D",  "D#", "E",  "F",
                                               "F#", "G",  "G#", "A",  "A#", "B"};
    const int n = clampNote(note);
    return std::string(kNames[n % 12]) + std::to_string(n / 12 - 1);
}

}  // namespace reasampler::instrument::ui
