#include "ccm/ui/YuGiOhCardEditDialog.hpp"
#include "ccm/ui/SwitchCtrl.hpp"
#include "ccm/ui/Theme.hpp"
#include "ccm/domain/Enums.hpp"
#include "ccm/util/CardLookupDetect.hpp"
#include "ccm/util/YuGiOhPrintingSlot.hpp"
#include "ccm/util/YuGiOhSetLookup.hpp"
#include <wx/app.h>
#include <wx/panel.h>
#include <algorithm>
#include <cctype>
#include <string>
#include <thread>
#include <unordered_set>

namespace ccm::ui {
namespace {
const char* const kRarityOptions[] = {
    "Common",
    "Rare",
    "Super Rare",
    "Ultra Rare",
    "Secret Rare",
    "Quarter Century Secret Rare",
    "Starlight Rare",
    "Collector's Rare",
    "Ghost Rare",
    "Ultimate Rare",
    "Platinum Secret Rare",
    "Prismatic Secret Rare",
};
}

YuGiOhCardEditDialog::YuGiOhCardEditDialog(wxWindow* parent,
                                           ImageService& imageService,
                                           SetService& setService,
                                           CardPreviewService& cardPreview,
                                           EditMode mode,
                                           YuGiOhCard initial,
                                           const std::vector<Set>* preloadedSets)
    : BaseCardEditDialog<YuGiOhCard>(
          parent,
          mode == EditMode::Create ? "Add Yu-Gi-Oh! Card" : "Edit Yu-Gi-Oh! Card",
          imageService, setService, mode, std::move(initial), Game::YuGiOh, preloadedSets),
      dialogMode_(mode),
      cardPreview_(cardPreview),
      variantFetchState_(std::make_shared<VariantFetchState>()) {
    buildAndPopulate();
    if (dialogMode_ == EditMode::Edit) {
        scheduleDeferredVariantPrefetch();
    }
}

YuGiOhCardEditDialog::~YuGiOhCardEditDialog() {
    if (variantFetchState_) variantFetchState_->alive = false;
}

void YuGiOhCardEditDialog::onCardLookupContextChanged() {
    clearCachedPrintVariants();
}

void YuGiOhCardEditDialog::buildFlagsRow(wxBoxSizer* flagsBox) {
    firstEditionCheck_ = new wxCheckBox(this, wxID_ANY, "1. Edition");
    signedCheck_       = new wxCheckBox(this, wxID_ANY, "Signed");
    alteredCheck_      = new wxCheckBox(this, wxID_ANY, "Altered");
    flagsBox->Add(firstEditionCheck_, 0, wxRIGHT, 12);
    flagsBox->Add(signedCheck_,       0, wxRIGHT, 12);
    flagsBox->Add(alteredCheck_,      0, wxRIGHT, 12);
}

void YuGiOhCardEditDialog::customizeSetPickerRow(wxBoxSizer& row, wxComboBox* combo) {
    wxWindow* const host = combo->GetParent();
    setCodeRowPanel_ = new wxPanel(host, wxID_ANY);
    auto* inner = new wxBoxSizer(wxHORIZONTAL);
    setCodeText_ = new wxTextCtrl(setCodeRowPanel_, wxID_ANY);
    setCodeAutoBtn_ = new wxButton(setCodeRowPanel_, wxID_ANY, "Auto detect");
    inner->Add(setCodeText_, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
    inner->Add(setCodeAutoBtn_, 0, wxALIGN_CENTER_VERTICAL);
    setCodeRowPanel_->SetSizer(inner);
    setCodeRowPanel_->Show(false);

    setModeHint_ = new wxStaticText(host, wxID_ANY, wxString());
    setPickerSwitch_ = new SwitchCtrl(host, wxID_ANY, false);
    setPickerSwitch_->Bind(EVT_CCM_SWITCH, &YuGiOhCardEditDialog::onSetRowSwitch, this);
    setCodeAutoBtn_->Bind(wxEVT_BUTTON, &YuGiOhCardEditDialog::onSetCodeAutoDetect, this);

    row.Add(combo, 1, wxEXPAND);
    row.Add(setCodeRowPanel_, 1, wxEXPAND);
    row.Add(setModeHint_, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
    row.Add(setPickerSwitch_, 0, wxALIGN_CENTER_VERTICAL);

    if (availableSets().empty()) {
        setPickerSwitch_->Enable(false);
    }
    syncSetModeHint();
}

void YuGiOhCardEditDialog::appendExtraRows(wxFlexGridSizer* grid) {
    auto* setNoPanel = new wxPanel(this, wxID_ANY);
    setNoCtrl_ = new wxTextCtrl(setNoPanel, wxID_ANY);
    setNoCtrl_->Bind(wxEVT_TEXT, &YuGiOhCardEditDialog::onSetNoTextChanged, this);
    autoSetNoBtn_ = new wxButton(setNoPanel, wxID_ANY, "Auto detect");
    autoSetNoBtn_->Bind(wxEVT_BUTTON, &YuGiOhCardEditDialog::onAutoDetectSetNo, this);
    nextSetNoBtn_ = new wxButton(setNoPanel, wxID_ANY, "Next");
    nextSetNoBtn_->Bind(wxEVT_BUTTON, &YuGiOhCardEditDialog::onNextSetNo, this);
    nextSetNoBtn_->Show(false);
    setNoFullPreview_ = new wxStaticText(setNoPanel, wxID_ANY, "(n/a)");
    auto* setNoRow = new wxBoxSizer(wxHORIZONTAL);
    setNoRow->Add(setNoCtrl_, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
    setNoRow->Add(autoSetNoBtn_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
    setNoRow->Add(nextSetNoBtn_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
    setNoRow->Add(setNoFullPreview_, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
    setNoPanel->SetSizer(setNoRow);

    auto* rarityPanel = new wxPanel(this, wxID_ANY);
    rarityChoice_ = new wxChoice(rarityPanel, wxID_ANY);
    autoRarityBtn_ = new wxButton(rarityPanel, wxID_ANY, "Auto detect");
    autoRarityBtn_->Bind(wxEVT_BUTTON, &YuGiOhCardEditDialog::onAutoDetectRarity, this);
    nextRarityBtn_ = new wxButton(rarityPanel, wxID_ANY, "Next");
    nextRarityBtn_->Bind(wxEVT_BUTTON, &YuGiOhCardEditDialog::onNextRarity, this);
    nextRarityBtn_->Show(false);
    wxArrayString rarityItems;
    rarityItems.Alloc(static_cast<int>(sizeof(kRarityOptions) / sizeof(kRarityOptions[0])));
    for (const char* rarity : kRarityOptions) {
        rarityItems.Add(wxString::FromUTF8(rarity));
    }
    rarityChoice_->Append(rarityItems);
    auto* rarityRow = new wxBoxSizer(wxHORIZONTAL);
    rarityRow->Add(rarityChoice_, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
    rarityRow->Add(autoRarityBtn_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
    rarityRow->Add(nextRarityBtn_, 0, wxALIGN_CENTER_VERTICAL);
    rarityPanel->SetSizer(rarityRow);

    appendRow(grid, "Set #", setNoPanel);
    appendRow(grid, "Rarity", rarityPanel);

    if (auto* setCombo = setComboControl()) {
        setCombo->Bind(wxEVT_COMBOBOX, &YuGiOhCardEditDialog::onSetSelectionChanged, this);
    }
}

void YuGiOhCardEditDialog::readExtraFromCard() {
    clearCachedPrintVariants();
    if (setNoCtrl_)         setNoCtrl_->ChangeValue(extractSetNoNumeric(constCard().setNo));
    if (rarityChoice_) {
        const wxString rarity = wxString::FromUTF8(constCard().rarity.c_str());
        int rarityIdx = rarityChoice_->FindString(rarity);
        if (rarityIdx == wxNOT_FOUND && !constCard().rarity.empty()) {
            rarityChoice_->Append(rarity);
            rarityIdx = rarityChoice_->GetCount() - 1;
        }
        if (rarityIdx == wxNOT_FOUND) rarityIdx = 0;
        if (rarityIdx != wxNOT_FOUND) rarityChoice_->SetSelection(rarityIdx);
    }
    if (firstEditionCheck_) firstEditionCheck_->SetValue(constCard().firstEdition);
    if (signedCheck_)       signedCheck_->SetValue(constCard().signed_);
    if (alteredCheck_)      alteredCheck_->SetValue(constCard().altered);
    refreshSetNoFullPreview();
}

void YuGiOhCardEditDialog::writeExtraToCard() {
    if (setNoCtrl_)         mutableCard().setNo        = composeFullSetNo(setNoCtrl_->GetValue().ToStdString(wxConvUTF8));
    if (rarityChoice_)      mutableCard().rarity       = rarityChoice_->GetStringSelection().ToStdString(wxConvUTF8);
    if (firstEditionCheck_) mutableCard().firstEdition = firstEditionCheck_->IsChecked();
    if (signedCheck_)       mutableCard().signed_      = signedCheck_->IsChecked();
    if (alteredCheck_)      mutableCard().altered      = alteredCheck_->IsChecked();
}

void YuGiOhCardEditDialog::clearCachedPrintVariants() {
    ++variantFetchEpoch_;
    cachedVariants_.clear();
    uniqueSetCodes_.clear();
    raritiesForCurrentSetCode_.clear();
    setCodeRingPos_ = 0;
    rarityRingPos_ = 0;
    refreshVariantNextControls();
}

void YuGiOhCardEditDialog::scheduleDeferredVariantPrefetch() {
    const unsigned epoch = variantFetchEpoch_;
    wxTheApp->CallAfter([this, epoch]() {
        prefetchVariantsForCurrentCardSilent(epoch);
    });
}

void YuGiOhCardEditDialog::prefetchVariantsForCurrentCardSilent(unsigned capturedEpoch) {
    if (capturedEpoch != variantFetchEpoch_) return;
    if (!cachedVariants_.empty()) return;
    const auto& card = constCard();
    if (card.name.empty() || card.set.name.empty()) return;

    auto detected = cardPreview_.detectPrintVariants(Game::YuGiOh, card.name, card.set.name);
    if (!detected) return;
    if (capturedEpoch != variantFetchEpoch_) return;

    cachedVariants_ = std::move(detected).value();
    rebuildVariantRingsFromCache();
    syncRingPositionsToControls();
    refreshVariantNextControls();
}

bool YuGiOhCardEditDialog::fetchAndCachePrintVariants() {
    syncCardFromControls();
    const auto& card = constCard();
    if (card.name.empty()) {
        showThemedMessageDialog(this, "Enter a card name first.", "Auto detect",
                                wxOK | wxICON_INFORMATION);
        return false;
    }
    if (card.set.name.empty()) {
        showThemedMessageDialog(this, "Select a set first.", "Auto detect",
                                wxOK | wxICON_INFORMATION);
        return false;
    }

    auto detected = cardPreview_.detectPrintVariants(Game::YuGiOh, card.name, card.set.name);
    if (!detected) {
        showThemedMessageDialog(this, "Auto detect failed: " + detected.error(), "Auto detect",
                                wxOK | wxICON_WARNING);
        return false;
    }
    cachedVariants_ = std::move(detected).value();
    return true;
}

void YuGiOhCardEditDialog::filterCachedVariantsForCardLanguage() {
    if (constCard().language != Language::English) return;
    const auto removed = std::remove_if(
        cachedVariants_.begin(), cachedVariants_.end(), [](const AutoDetectedPrint& p) {
            return ygoLikelyEuropeanRegionalSetCode(p.setNo);
        });
    cachedVariants_.erase(removed, cachedVariants_.end());
}

void YuGiOhCardEditDialog::rebuildVariantRingsFromCache() {
    syncCardFromControls();
    uniqueSetCodes_.clear();
    raritiesForCurrentSetCode_.clear();
    if (cachedVariants_.empty()) return;

    filterCachedVariantsForCardLanguage();

    std::unordered_set<std::string> seenSlots;
    seenSlots.reserve(cachedVariants_.size());
    for (const auto& p : cachedVariants_) {
        if (p.setNo.empty()) continue;
        const std::string digits = ygoCollectorDigitsOnly(p.setNo);
        if (digits.empty()) continue;
        const std::string slotKey = ygoAbbrevBeforeDash(p.setNo) + "|" + digits;
        if (!seenSlots.insert(slotKey).second) continue;
        uniqueSetCodes_.push_back(p.setNo);
    }

    const std::string full = currentFullSetNoFromControls();
    std::unordered_set<std::string> seenRarity;
    for (const auto& p : cachedVariants_) {
        if (!ygoPrintingSlotsMatch(p.setNo, full)) continue;
        if (p.rarity.empty()) continue;
        if (seenRarity.insert(p.rarity).second) raritiesForCurrentSetCode_.push_back(p.rarity);
    }
}

void YuGiOhCardEditDialog::syncRingPositionsToControls() {
    const std::string full = currentFullSetNoFromControls();
    setCodeRingPos_ = 0;
    for (std::size_t i = 0; i < uniqueSetCodes_.size(); ++i) {
        if (ygoPrintingSlotsMatch(uniqueSetCodes_[i], full)) {
            setCodeRingPos_ = i;
            break;
        }
    }

    rarityRingPos_ = 0;
    if (rarityChoice_) {
        const std::string r = rarityChoice_->GetStringSelection().ToStdString(wxConvUTF8);
        for (std::size_t i = 0; i < raritiesForCurrentSetCode_.size(); ++i) {
            if (raritiesForCurrentSetCode_[i] == r) {
                rarityRingPos_ = i;
                break;
            }
        }
    }
}

void YuGiOhCardEditDialog::applyRarityStringToChoice(const std::string& rarity) {
    if (!rarityChoice_) return;
    const wxString wxRare = wxString::FromUTF8(rarity.c_str());
    int idx = rarityChoice_->FindString(wxRare);
    if (idx == wxNOT_FOUND && !rarity.empty()) {
        rarityChoice_->Append(wxRare);
        idx = rarityChoice_->GetCount() - 1;
    }
    if (idx != wxNOT_FOUND) rarityChoice_->SetSelection(idx);
}

std::string YuGiOhCardEditDialog::currentFullSetNoFromControls() const {
    if (!setNoCtrl_) return {};
    return composeFullSetNo(setNoCtrl_->GetValue().ToStdString(wxConvUTF8));
}

void YuGiOhCardEditDialog::refreshVariantNextControls() {
    if (!nextSetNoBtn_ || !nextRarityBtn_) return;
    nextSetNoBtn_->Show(uniqueSetCodes_.size() > 1);
    nextRarityBtn_->Show(raritiesForCurrentSetCode_.size() > 1);
    Layout();
    if (GetSizer()) Fit();
}

void YuGiOhCardEditDialog::onAutoDetectSetNo(wxCommandEvent&) {
    syncCardFromControls();
    const auto& card = constCard();
    if (card.set.id.empty()) {
        showThemedMessageDialog(this, "Select a set first.", "Auto detect",
                                wxOK | wxICON_INFORMATION);
        return;
    }

    // Trim so a blank-looking Name field does not take the name→setNo path.
    std::string name = card.name;
    while (!name.empty() && std::isspace(static_cast<unsigned char>(name.front()))) {
        name.erase(name.begin());
    }
    while (!name.empty() && std::isspace(static_cast<unsigned char>(name.back()))) {
        name.pop_back();
    }

    const std::string setNoDigits =
        setNoCtrl_ ? setNoCtrl_->GetValue().ToStdString(wxConvUTF8) : std::string();
    const bool nameEmpty = name.empty();
    const bool setNoEmpty = ygoCollectorDigitsFromInput(setNoDigits).empty();

    if (nameEmpty && setNoEmpty) {
        showThemedMessageDialog(this, "Enter a card name or set number.", "Auto detect",
                                wxOK | wxICON_INFORMATION);
        return;
    }

    if (shouldDetectBySetNo(nameEmpty, setNoEmpty)) {
        const unsigned epoch = variantFetchEpoch_;
        requestBySetNoAsync(epoch, card.set.id, card.set.name, setNoDigits);
        return;
    }

    autoDetectFromApi(true, true);
}

void YuGiOhCardEditDialog::requestBySetNoAsync(unsigned capturedEpoch, std::string setId,
                                               std::string setName, std::string setNo) {
    if (capturedEpoch != variantFetchEpoch_) return;
    if (autoSetNoBtn_) autoSetNoBtn_->Disable();

    auto state = variantFetchState_;
    CardPreviewService* svc = &cardPreview_;
    YuGiOhCardEditDialog* self = this;
    std::thread([state, svc, self, capturedEpoch, setId = std::move(setId),
                 setName = std::move(setName), setNo = std::move(setNo)]() {
        // Prefer set id (offline catalog pack key); fall back to display name
        // for YGOPRODeck cardset= when the catalog is missing.
        auto detected = svc->detectVariantsBySetNo(Game::YuGiOh, setId, setNo);
        if (!detected && !setName.empty() && setName != setId) {
            detected = svc->detectVariantsBySetNo(Game::YuGiOh, setName, setNo);
        }
        wxTheApp->CallAfter([state, self, capturedEpoch,
                             detected = std::move(detected)]() mutable {
            if (!state->alive.load()) return;
            self->applyReverseDetectedList(capturedEpoch, std::move(detected));
        });
    }).detach();
}

void YuGiOhCardEditDialog::applyReverseDetectedList(
    unsigned capturedEpoch,
    Result<std::vector<AutoDetectedPrint>> detected) {
    if (capturedEpoch != variantFetchEpoch_) return;
    if (autoSetNoBtn_) autoSetNoBtn_->Enable();

    if (!detected) {
        showThemedMessageDialog(this, "Auto detect failed: " + detected.error(),
                                "Auto detect", wxOK | wxICON_WARNING);
        return;
    }
    if (detected.value().empty()) {
        showThemedMessageDialog(this, "No matching Yu-Gi-Oh! card found.", "Auto detect",
                                wxOK | wxICON_INFORMATION);
        return;
    }

    cachedVariants_ = std::move(detected).value();
    const auto& p = cachedVariants_.front();
    if (auto* name = nameControl(); name && !p.name.empty()) {
        name->ChangeValue(wxString::FromUTF8(p.name.c_str()));
    }
    if (setNoCtrl_ && !p.setNo.empty()) {
        setNoCtrl_->ChangeValue(wxString::FromUTF8(extractSetNoNumeric(p.setNo).c_str()));
    }
    if (!p.rarity.empty()) applyRarityStringToChoice(p.rarity);
    rebuildVariantRingsFromCache();
    syncRingPositionsToControls();
    refreshSetNoFullPreview();
    refreshVariantNextControls();
}

void YuGiOhCardEditDialog::onAutoDetectRarity(wxCommandEvent&) {
    autoDetectFromApi(false, true);
}

void YuGiOhCardEditDialog::onNextSetNo(wxCommandEvent&) {
    if (uniqueSetCodes_.size() <= 1) return;
    setCodeRingPos_ = (setCodeRingPos_ + 1) % uniqueSetCodes_.size();
    const std::string& code = uniqueSetCodes_[setCodeRingPos_];
    if (setNoCtrl_) {
        setNoCtrl_->ChangeValue(wxString::FromUTF8(extractSetNoNumeric(code).c_str()));
    }
    for (const auto& p : cachedVariants_) {
        if (p.setNo == code) {
            applyRarityStringToChoice(p.rarity);
            break;
        }
    }
    rebuildVariantRingsFromCache();
    syncRingPositionsToControls();
    refreshSetNoFullPreview();
    refreshVariantNextControls();
}

void YuGiOhCardEditDialog::onNextRarity(wxCommandEvent&) {
    if (raritiesForCurrentSetCode_.size() <= 1) return;
    rarityRingPos_ = (rarityRingPos_ + 1) % raritiesForCurrentSetCode_.size();
    applyRarityStringToChoice(raritiesForCurrentSetCode_[rarityRingPos_]);
    refreshVariantNextControls();
}

void YuGiOhCardEditDialog::autoDetectFromApi(bool fillSetNo, bool fillRarity) {
    if (!fetchAndCachePrintVariants()) return;

    if (fillSetNo && setNoCtrl_ && !cachedVariants_.empty()) {
        const auto& p = cachedVariants_.front();
        setNoCtrl_->ChangeValue(wxString::FromUTF8(extractSetNoNumeric(p.setNo).c_str()));
        if (fillRarity) applyRarityStringToChoice(p.rarity);
    } else if (fillRarity && rarityChoice_) {
        const std::string full = currentFullSetNoFromControls();
        bool applied = false;
        for (const auto& p : cachedVariants_) {
            if (ygoPrintingSlotsMatch(p.setNo, full)) {
                applyRarityStringToChoice(p.rarity);
                applied = true;
                break;
            }
        }
        if (!applied && !cachedVariants_.empty()) {
            applyRarityStringToChoice(cachedVariants_.front().rarity);
        }
    }

    rebuildVariantRingsFromCache();
    syncRingPositionsToControls();
    refreshSetNoFullPreview();
    refreshVariantNextControls();
}

void YuGiOhCardEditDialog::onSetNoTextChanged(wxCommandEvent&) {
    markSetNoLookupEdited();
    refreshSetNoFullPreview();
    if (!cachedVariants_.empty()) {
        rebuildVariantRingsFromCache();
        syncRingPositionsToControls();
        refreshVariantNextControls();
    }
}

void YuGiOhCardEditDialog::onSetSelectionChanged(wxCommandEvent& ev) {
    handleSetSelectionChanged();
    ev.Skip();
}

void YuGiOhCardEditDialog::onSetSelectionApplied() {
    handleSetSelectionChanged();
}

void YuGiOhCardEditDialog::handleSetSelectionChanged() {
    clearCachedPrintVariants();
    refreshSetNoFullPreview();
    scheduleDeferredVariantPrefetch();
}

void YuGiOhCardEditDialog::syncSetModeHint() {
    if (!setModeHint_ || !setPickerSwitch_) return;
    // Switch on = set-code entry; hint tells user how to return to the name list.
    setModeHint_->SetLabel(setPickerSwitch_->GetValue() ? wxString::FromUTF8("Set name")
                                                       : wxString::FromUTF8("Set code"));
}

void YuGiOhCardEditDialog::onSetRowSwitch(wxCommandEvent&) {
    if (!setPickerSwitch_ || !setComboControl() || !setCodeRowPanel_) return;
    syncSetModeHint();
    const bool codeMode = setPickerSwitch_->GetValue();
    setComboControl()->Show(!codeMode);
    setCodeRowPanel_->Show(codeMode);
    wxWindow* host = setComboControl()->GetParent();
    if (host) {
        host->Layout();
    }
    Layout();
}

void YuGiOhCardEditDialog::onSetCodeAutoDetect(wxCommandEvent&) {
    if (!setCodeText_ || !setPickerSwitch_) return;
    const auto& sets = availableSets();
    if (sets.empty()) {
        showThemedMessageDialog(this,
                                "No sets are cached. Use Sets > Update Yu-Gi-Oh! first.",
                                "Set code", wxOK | wxICON_INFORMATION);
        return;
    }

    const std::string raw = setCodeText_->GetValue().ToStdString(wxConvUTF8);
    const auto r = lookupYuGiOhSetByShorthand(raw, sets);
    using Kind = YuGiOhSetShorthandLookup::Kind;
    if (r.kind == Kind::NotFound) {
        showThemedMessageDialog(
            this,
            "No set matches that code. Check the code spelling or use Sets > Update Yu-Gi-Oh! to refresh the list.",
            "Set code", wxOK | wxICON_INFORMATION);
        return;
    }
    if (r.kind == Kind::Ambiguous) {
        showThemedMessageDialog(this,
                                "Multiple cached sets match that code. Refresh the set list or pick the set from the list.",
                                "Set code", wxOK | wxICON_INFORMATION);
        return;
    }

    applySetSelectionByIndex(r.index);

    setPickerSwitch_->SetValue(false, false);
    syncSetModeHint();
    setComboControl()->Show(true);
    setCodeRowPanel_->Show(false);
    wxWindow* host = setComboControl()->GetParent();
    if (host) {
        host->Layout();
    }
    Layout();
}

std::string YuGiOhCardEditDialog::extractSetNoNumeric(std::string_view fullSetNo) const {
    std::string digits;
    for (char ch : fullSetNo) {
        if (std::isdigit(static_cast<unsigned char>(ch))) digits.push_back(ch);
    }
    return digits;
}

std::string YuGiOhCardEditDialog::composeFullSetNo(std::string_view numericRaw) const {
    std::string numeric;
    for (char ch : numericRaw) {
        if (std::isdigit(static_cast<unsigned char>(ch))) numeric.push_back(ch);
    }
    const Set* set = selectedSetFromControls();
    if (!set || set->id.empty() || numeric.empty()) return numeric;
    return set->id + "-" + numeric;
}

void YuGiOhCardEditDialog::refreshSetNoFullPreview() {
    if (!setNoFullPreview_ || !setNoCtrl_) return;
    const std::string full = composeFullSetNo(setNoCtrl_->GetValue().ToStdString(wxConvUTF8));
    if (full.empty()) {
        setNoFullPreview_->SetLabel("(n/a)");
    } else {
        setNoFullPreview_->SetLabel(wxString::Format("(%s)", wxString::FromUTF8(full.c_str())));
    }
    setNoFullPreview_->GetParent()->Layout();
}

}  // namespace ccm::ui
