// tail_control — pure implementation. See tail_control.h.

#include "core/capture/tail_control.h"

#include <algorithm>
#include <cstdio>

#include "core/json/json.h"

namespace reasampler::capture {

TailMode cycleTailMode(TailMode current) {
    switch (current) {
        case TailMode::None:   return TailMode::Auto;
        case TailMode::Auto:   return TailMode::Manual;
        case TailMode::Manual: return TailMode::None;
    }
    return TailMode::None;  // unreachable for a valid enum; fail to the safe default
}

double clampManualMs(double manualMs) {
    return std::clamp(manualMs, 0.0, kMaxTailMs);
}

double adjustManualMs(double current, int notches, double stepMs) {
    return clampManualMs(current + notches * stepMs);
}

std::string tailToggleLabel(const TailSetting& setting) {
    switch (setting.mode) {
        case TailMode::None: return "Tail: Off";
        case TailMode::Auto: return "Tail: Auto";
        case TailMode::Manual: {
            // Clamped so the readout can't show an over-cap value even if
            // manualMs was stored past the cap.
            const double seconds = clampManualMs(setting.manualMs) / 1000.0;
            char buf[32];
            std::snprintf(buf, sizeof(buf), "Tail: Manual %.1fs", seconds);
            return std::string(buf);
        }
    }
    return "Tail: Off";  // unreachable for a valid enum; fail to the safe default
}

// --- JSON round-trip ---------------------------------------------------------
// manualMs round-trips exactly (json::numToStr uses the shortest %.17g-class
// form for doubles); deserialize returns nullopt on any parse failure.

namespace {

// The persisted integer for a mode. Stable forever (stored in the .rpp): never
// renumber these values or an already-saved project reads back the wrong mode.
int modeToInt(TailMode m) {
    switch (m) {
        case TailMode::None:   return 0;
        case TailMode::Auto:   return 1;
        case TailMode::Manual: return 2;
    }
    return 0;
}

std::optional<TailMode> modeFromInt(int v) {
    switch (v) {
        case 0: return TailMode::None;
        case 1: return TailMode::Auto;
        case 2: return TailMode::Manual;
        default: return std::nullopt;  // unknown enumerant -> malformed -> default
    }
}

}  // namespace

std::string serializeTailSetting(const TailSetting& setting) {
    // Byte-identical to the former snprintf writer: {"mode":%d,"manualMs":%.17g}.
    std::string out;
    {
        json::Writer w(out);
        w.keyRaw("mode", json::numToStr(modeToInt(setting.mode)));
        w.keyRaw("manualMs", json::numToStr(setting.manualMs));
    }  // Writer closes the object here (see bank_model's NRVO note)
    return out;
}

std::optional<TailSetting> deserializeTailSetting(const std::string& blob) {
    json::Reader r(blob);
    if (!r.consume('{')) return std::nullopt;

    int modeInt = 0;
    double ms = 0.0;
    bool haveMode = false, haveMs = false;
    r.skipWs();
    if (!r.consume('}')) {
        do {
            std::string key;
            if (!r.parseKey(key)) return std::nullopt;
            if (key == "mode") {
                if (!r.parseInt(modeInt)) return std::nullopt;
                haveMode = true;
            } else if (key == "manualMs") {
                if (!r.parseDouble(ms)) return std::nullopt;
                haveMs = true;
            } else {
                if (!r.skipValue()) return std::nullopt;  // forward-compat
            }
        } while (r.consume(','));
        if (!r.consume('}')) return std::nullopt;
    }
    if (!haveMode || !haveMs) return std::nullopt;  // absent key -> malformed -> default

    const std::optional<TailMode> mode = modeFromInt(modeInt);
    if (!mode) return std::nullopt;

    TailSetting out;
    out.mode = *mode;
    out.manualMs = ms;
    return out;
}

} // namespace reasampler::capture
