// bank_sync.cpp — see bank_sync.h. Pure; standard library only.

#include "bank_sync.h"

#include <cstdint>
#include <limits>
#include <string>

namespace reasampler::vst {

std::int64_t parseBankGeneration(const std::string& raw) {
    if (raw.empty()) return kBankGenerationAbsent;

    // Whole-string, non-negative decimal parse WITHOUT exceptions or locale surprises.
    // A leading '+' / '-' , any non-digit, an empty digit run, or overflow past int64 max
    // all reject to the absent default (0). Manual accumulation with an overflow guard so a
    // pathologically long digit run can never wrap into a bogus small value.
    std::int64_t value = 0;
    constexpr std::int64_t kMax = std::numeric_limits<std::int64_t>::max();
    for (const char c : raw) {
        if (c < '0' || c > '9') return kBankGenerationAbsent;  // any non-digit -> reject whole
        const int digit = c - '0';
        // Guard value*10 + digit against overflow before performing it.
        if (value > (kMax - digit) / 10) return kBankGenerationAbsent;  // would overflow -> reject
        value = value * 10 + digit;
    }
    return value;
}

std::string formatBankGeneration(std::int64_t generation) {
    // Non-negative decimal; a negative (should never be produced by the writer) formats as
    // its std::to_string form and would parse back to 0, so the writer's monotonic counter
    // stays in the >= 0 domain by construction.
    return std::to_string(generation);
}

bool bankGenerationChanged(std::int64_t seen, std::int64_t current) {
    return current != seen;
}

AssignConsumeDecision consumeDecision(const std::optional<AssignmentRequest>& request,
                                      std::int64_t lastConsumed, bool resolves,
                                      bool isFocusedTarget) {
    AssignConsumeDecision d;
    d.consumedGeneration = lastConsumed;  // default: nothing changes

    // Rule 1: no request, or not newer than what we already consumed -> nothing new.
    if (!request) return d;
    if (request->generation <= lastConsumed) return d;

    // Rule 2: a new request, but this instance is not the target -> do not act, do NOT
    // advance the marker (stay eligible if focus later lands here). No thundering herd.
    if (!isFocusedTarget) return d;

    // The request is new AND we are the target: it will be consumed-as-seen either way, so
    // advance the marker to its generation so it is never re-evaluated.
    d.consumedGeneration = request->generation;

    // Rule 3: unresolvable (bankId, sampleId) -> DROP silently (reader requirement): marker
    // advanced above, but no selection change.
    if (!resolves) return d;

    // Rule 4: new, target, resolvable -> apply the selection.
    d.apply = true;
    d.bankId = request->bankId;
    d.sampleId = request->sampleId;
    return d;
}

}  // namespace reasampler::vst
