// editor_paint_browse.cpp — the Browse modal's painter: the full-window
// select-then-confirm picker (wash, search box, filter tabs, card grid, scrollbar, footer).
// Windows-only. Shares the Sample face's title-band + empty-state painters via the class +
// editor_internal.h.

#include "shell/instrument/reasampler_editor.h"

#ifdef _WIN32

#include <algorithm>
#include <cstdint>
#include <string>
#include <vector>

#include "core/instrument/ui/browser_scroll.h"  // BrowseModal + scroll/search geometry
#include "shell/instrument/editor_internal.h"   // kit adapters + labels
#include "shell/instrument/reasampler_processor.h"

namespace reasampler::vst {

using namespace reasampler::ui;              // kit vocabulary
using namespace reasampler::instrument::ui;  // browser geometry
using namespace reasampler::instrument::map; // SampleChoice / BankChoice

void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) {
    // A full-window modal sheet over the Sample face. Dim the underlying Sample face with a
    // bg/base wash, then draw the picker opaque on top.
    LICE_FillRect(bmp, 0, 0, w, h, toLice(roleColor(Role::BgBase)), 0.82f, 0);
    const BrowseModal bm = computeBrowseModal(w, h);

    // Title band + Back button (returns to Sample, discarding any pending pick).
    drawTitleBand(bmp, bm.title, "Browse - pick a capture");
    {
        const KitButtonBox box{toKitBox(bm.back)};
        const InteractionState st =
            isHovered(HoverKind::kBack, -1) ? InteractionState::Hover : InteractionState::Rest;
        drawButton(bmp, box, "Back", st, /*warn=*/false);
    }

    // Search box (type-to-filter). A focused box lifts to Focus + a ring; else Rest/Hover.
    const Rect searchAbs = bm.search;
    const InteractionState searchState =
        searchFocused_ ? InteractionState::Focus
                       : (isHovered(HoverKind::kSearchBox, -1) ? InteractionState::Hover
                                                              : InteractionState::Rest);
    fillSurface(bmp, toKitBox(searchAbs), Role::BgCell, searchState);
    if (searchFocused_) {
        LICE_DrawRect(bmp, searchAbs.x, searchAbs.y, searchAbs.width - 1,
                      searchAbs.height - 1, toLice(roleColor(Role::TextPrimary)), 1.0f, 0);
    }
    {
        std::string sb = searchQuery_.empty()
            ? std::string("Search captures...")
            : ("Search: " + searchQuery_ + (searchFocused_ ? "_" : ""));
        Rect sbText = Rect::ltrb(searchAbs.x + 6, searchAbs.y, searchAbs.right() - 6, searchAbs.bottom());
        kitText(bmp, sbText, sb.c_str(), Font::Label,
                searchQuery_.empty() ? Role::TextDim : Role::TextPrimary);
    }

    // Tabs + card grid, laid out over the content sub-area by the pure module (origin-offset).
    const Rect browserArea = bm.content;
    const BrowserLayout bl = layoutBrowser(browserArea.width, browserArea.height);
    const int ox = browserArea.x;
    const int oy = browserArea.y;
    scrollOffset_ = clampScrollOffset(bl, static_cast<int>(visible_.size()), scrollOffset_);

    const int tabCount = static_cast<int>(banks_.size()) + 1;
    for (int i = 0; i < tabCount; ++i) {
        Rect t = filterTabRect(bl, tabCount, i);
        t = Rect::ltrb(t.x + ox, t.y + oy, t.right() + ox, t.bottom() + oy);
        const std::string label = (i == 0) ? "All" : banks_[static_cast<std::size_t>(i - 1)].displayName;
        const bool active = (i == 0) ? activeFilterBankId_.empty()
                                     : (banks_[static_cast<std::size_t>(i - 1)].id == activeFilterBankId_);
        const InteractionState state =
            active ? InteractionState::Active
                   : (isHovered(HoverKind::kFilterTab, i) ? InteractionState::Hover
                                                          : InteractionState::Rest);
        fillSurface(bmp, toKitBox(t), Role::BgCell, state);
        kitTextCentered(bmp, t, label.c_str(), Font::Label,
                        active ? Role::BgBase : Role::TextPrimary);
    }

    // Cards (the visible window at the current scroll offset). The pending pick
    // (browsePendingId_) is marked with the accent-primary border; the currently-loaded id
    // gets a faint tertiary border.
    const int bins = thumbBins(bl);
    const int cardCount = static_cast<int>(visible_.size());
    const VisibleRange vr = visibleCardRange(bl, cardCount, scrollOffset_);
    for (int i = vr.first; i < vr.last; ++i) {
        Rect content = cardContentRect(bl, i);
        Rect thumb = cardThumbnailRect(bl, i);
        Rect labelR = cardLabelRect(bl, i);
        content = Rect::ltrb(content.x + ox, content.y + oy - scrollOffset_,
                       content.right() + ox, content.bottom() + oy - scrollOffset_);
        thumb = Rect::ltrb(thumb.x + ox, thumb.y + oy - scrollOffset_,
                     thumb.right() + ox, thumb.bottom() + oy - scrollOffset_);
        labelR = Rect::ltrb(labelR.x + ox, labelR.y + oy - scrollOffset_,
                      labelR.right() + ox, labelR.bottom() + oy - scrollOffset_);

        const SampleChoice& s = visible_[static_cast<std::size_t>(i)];
        const bool pending = (s.id == browsePendingId_);
        const bool loaded = (s.id == selectedId_);
        const InteractionState cardState =
            isHovered(HoverKind::kCard, i) ? InteractionState::Hover : InteractionState::Rest;
        fillSurface(bmp, toKitBox(content), Role::BgCell, cardState);
        const KitColor cardBorder = pending ? roleColor(Role::AccentPrimary)
                                   : (loaded ? roleColor(Role::AccentTertiary)
                                             : roleColor(Role::LineHairline));
        LICE_DrawRect(bmp, content.x, content.y, content.width - 1, content.height - 1,
                      toLice(cardBorder), 1.0f, 0);
        drawEnvelope(bmp, thumb, thumbnailFor(s.id, bins));

        std::string caption = s.displayName.empty() ? s.id : s.displayName;
        Rect nameR = Rect::ltrb(labelR.x + 3, labelR.y, labelR.right() - 3, labelR.y + labelR.height / 2);
        Rect badgeR = Rect::ltrb(labelR.x + 3, nameR.bottom(), labelR.right() - 3, labelR.bottom());
        kitText(bmp, nameR, caption.c_str(), Font::Label, Role::TextPrimary);
        std::string badge;
        if (s.rootNote) badge = "root " + noteLabel(*s.rootNote);
        else if (s.key) badge = *s.key;
        else badge = "root -";
        kitText(bmp, badgeR, badge.c_str(), Font::Micro, Role::TextDim);
    }

    // Scrollbar thumb.
    {
        const Rect thumb = scrollThumbRect(bl, cardCount, scrollOffset_);
        if (thumb.height > 0) {
            const bool dragging = (drag_ == DragKind::kScrollThumb);
            const KitColor tc = roleColor(dragging ? Role::AccentHot : Role::AccentPrimary);
            LICE_FillRect(bmp, thumb.x + ox, thumb.y + oy, thumb.width, thumb.height,
                          toLice(tc), 0.8f, 0);
        }
    }

    if (visible_.empty()) paintEmptyState(bmp, browserArea);

    // Footer: Cancel (discard, return to Sample) + Load (commit the pending pick). Load is inert
    // (no accent) until a card is picked. Draw a footer strip so the buttons read as a modal bar.
    Rect footer = Rect::ltrb(0, bm.content.bottom(), w, h);
    fillSurface(bmp, toKitBox(footer), Role::BgPanel, InteractionState::Rest);
    {
        const KitButtonBox box{toKitBox(bm.cancel)};
        const InteractionState st =
            isHovered(HoverKind::kBrowseCancel, -1) ? InteractionState::Hover : InteractionState::Rest;
        drawButton(bmp, box, "Cancel", st, /*warn=*/false);
    }
    {
        const KitButtonBox box{toKitBox(bm.confirm)};
        const bool armed = !browsePendingId_.empty();
        const InteractionState st = armed
            ? (isHovered(HoverKind::kBrowseConfirm, -1) ? InteractionState::Hover : InteractionState::Active)
            : InteractionState::Rest;
        drawButton(bmp, box, "Load", st, /*warn=*/false);
    }
}

}  // namespace reasampler::vst

#endif  // _WIN32
