#include "core/model/bank_model.h"

#include "core/json/json.h"
#include "core/util/relative_path.h"

// bank_model implementation. JSON rides on the shared core/json lexical layer;
// only the Sample/index DOMAIN grammar lives here. Doubles are emitted with 17
// significant digits (%.17g), the shortest form that round-trips every
// IEEE-754 double exactly, so deserialize(serialize(x)) == x holds bit-for-bit.

namespace reasampler::model {

// -- equality -----------------------------------------------------------

bool SourceRange::operator==(const SourceRange& o) const {
    return startSeconds == o.startSeconds && endSeconds == o.endSeconds &&
           startPpq == o.startPpq && endPpq == o.endPpq;
}

bool Provenance::operator==(const Provenance& o) const {
    return parentSampleId == o.parentSampleId && fxChainSnapshot == o.fxChainSnapshot;
}

bool Levels::operator==(const Levels& o) const {
    return peakDb == o.peakDb && rmsDb == o.rmsDb && lufs == o.lufs;
}

bool LoopPoints::operator==(const LoopPoints& o) const {
    return start == o.start && end == o.end;
}

bool Sample::operator==(const Sample& o) const {
    return id == o.id && displayName == o.displayName && relativePath == o.relativePath &&
           sourceMode == o.sourceMode && sourceRange == o.sourceRange &&
           trackGuids == o.trackGuids && wetDry == o.wetDry &&
           channelCount == o.channelCount && sampleRate == o.sampleRate &&
           lengthSeconds == o.lengthSeconds && lengthBeats == o.lengthBeats &&
           captureTempo == o.captureTempo &&
           captureTimeSigNum == o.captureTimeSigNum &&
           captureTimeSigDenom == o.captureTimeSigDenom && key == o.key &&
           rootNote == o.rootNote && loop == o.loop && levels == o.levels &&
           clipped == o.clipped && tier == o.tier && contentHash == o.contentHash &&
           provenance == o.provenance && createdTimestamp == o.createdTimestamp;
}

using util::isAbsolutePath;

// -- BankModel ------------------------------------------------------------

AddResult BankModel::add(const Sample& sample) {
    if (sample.id.empty()) return AddResult::RejectedEmptyId;
    if (isAbsolutePath(sample.relativePath)) return AddResult::RejectedAbsolutePath;

    if (findByHash(sample.contentHash) != nullptr)
        return AddResult::Collapsed;

    samples_.push_back(sample);
    return AddResult::Added;
}

bool BankModel::remove(const std::string& id) {
    for (auto it = samples_.begin(); it != samples_.end(); ++it) {
        if (it->id == id) {
            samples_.erase(it);
            return true;
        }
    }
    return false;
}

bool BankModel::updateInPlace(const std::string& id, const Sample& updated) {
    if (isAbsolutePath(updated.relativePath)) return false;  // invariant still holds
    for (auto& s : samples_) {
        if (s.id == id) {
            s = updated;  // replace in place — position (insertion order) preserved
            return true;
        }
    }
    return false;
}

const Sample* BankModel::query(const std::string& id) const {
    for (const auto& s : samples_)
        if (s.id == id) return &s;
    return nullptr;
}

const Sample* BankModel::findByHash(const std::string& contentHash) const {
    if (contentHash.empty()) return nullptr;  // empty hashes never dedup
    for (const auto& s : samples_)
        if (s.contentHash == contentHash) return &s;
    return nullptr;
}

bool BankModel::moveTier(const std::string& id, Tier tier) {
    for (auto& s : samples_) {
        if (s.id == id) {
            s.tier = tier;
            return true;
        }
    }
    return false;
}

std::vector<Sample> BankModel::byTier(Tier tier) const {
    std::vector<Sample> out;
    for (const auto& s : samples_)
        if (s.tier == tier) out.push_back(s);
    return out;
}

// -- JSON writer ------------------------------------------------------------

namespace {

using json::numToStr;
using json::writeEscaped;
using json::writeStringArray;
using ObjWriter = json::Writer;

void writeSample(std::string& out, const Sample& s) {
    ObjWriter w(out);
    w.keyStr("id", s.id);
    w.keyStr("displayName", s.displayName);
    w.keyStr("relativePath", s.relativePath);
    w.keyRaw("sourceMode", numToStr(static_cast<int>(s.sourceMode)));

    w.keyBegin("sourceRange");
    {
        ObjWriter r(out);
        r.keyRaw("startSeconds", numToStr(s.sourceRange.startSeconds));
        r.keyRaw("endSeconds", numToStr(s.sourceRange.endSeconds));
        r.keyRaw("startPpq", numToStr(s.sourceRange.startPpq));
        r.keyRaw("endPpq", numToStr(s.sourceRange.endPpq));
    }

    w.keyBegin("trackGuids");
    writeStringArray(out, s.trackGuids);

    w.keyRaw("wetDry", numToStr(s.wetDry));
    w.keyRaw("channelCount", numToStr(s.channelCount));
    w.keyRaw("sampleRate", numToStr(s.sampleRate));
    w.keyRaw("lengthSeconds", numToStr(s.lengthSeconds));
    w.keyRaw("lengthBeats", numToStr(s.lengthBeats));
    w.keyRaw("captureTempo", numToStr(s.captureTempo));
    w.keyRaw("captureTimeSigNum", numToStr(s.captureTimeSigNum));
    w.keyRaw("captureTimeSigDenom", numToStr(s.captureTimeSigDenom));

    // Optionals are emitted as null when absent so present/absent round-trips.
    w.keyBegin("key");
    if (s.key) writeEscaped(out, *s.key); else out += "null";

    // Emitted as null when absent (same shape as `key`/`provenance`) so JSON that
    // lacks these keys entirely parses to empty optionals and re-serializes
    // without invention.
    w.keyBegin("rootNote");
    if (s.rootNote) out += numToStr(*s.rootNote); else out += "null";

    w.keyBegin("loop");
    if (s.loop) {
        ObjWriter lp(out);
        lp.keyRaw("start", numToStr(s.loop->start));
        lp.keyRaw("end", numToStr(s.loop->end));
    } else {
        out += "null";
    }

    w.keyBegin("levels");
    {
        ObjWriter l(out);
        l.keyRaw("peakDb", numToStr(s.levels.peakDb));
        l.keyRaw("rmsDb", numToStr(s.levels.rmsDb));
        l.keyRaw("lufs", numToStr(s.levels.lufs));
    }

    w.keyRaw("clipped", s.clipped ? "true" : "false");
    w.keyRaw("tier", numToStr(static_cast<int>(s.tier)));
    w.keyStr("contentHash", s.contentHash);

    w.keyBegin("provenance");
    if (s.provenance) {
        ObjWriter p(out);
        p.keyStr("parentSampleId", s.provenance->parentSampleId);
        p.keyStr("fxChainSnapshot", s.provenance->fxChainSnapshot);
    } else {
        out += "null";
    }

    w.keyRaw("createdTimestamp", numToStr(s.createdTimestamp));
}

} // namespace

std::string BankModel::serialize() const {
    std::string out;
    {
        ObjWriter root(out);
        root.keyRaw("version", numToStr(1));
        root.keyBegin("samples");
        out += '[';
        for (std::size_t i = 0; i < samples_.size(); ++i) {
            if (i) out += ',';
            writeSample(out, samples_[i]);
        }
        out += ']';
    }  // root closes the object here — not deferred to function return (NRVO would
       // otherwise let the caller observe `out` before the closing brace is appended)
    return out;
}

// -- JSON parser (recursive descent over the shared json::Reader) -----------
// Returns false on any malformed input; never reads out of bounds. Only
// supports the subset our writer emits.

namespace {

bool parseSample(json::Reader& r, Sample& s) {
    if (!r.consume('{')) return false;
    r.skipWs();
    if (r.consume('}')) return true;  // empty object (shouldn't happen, but valid)

    do {
        std::string key;
        if (!r.parseKey(key)) return false;

        if (key == "id") {
            if (!r.parseString(s.id)) return false;
        } else if (key == "displayName") {
            if (!r.parseString(s.displayName)) return false;
        } else if (key == "relativePath") {
            if (!r.parseString(s.relativePath)) return false;
        } else if (key == "sourceMode") {
            int v = 0;
            if (!r.parseInt(v)) return false;
            // Valid range: MasterMix(0) .. Realtime(5).
            if (v < static_cast<int>(SourceMode::MasterMix) ||
                v > static_cast<int>(SourceMode::Realtime))
                return false;
            s.sourceMode = static_cast<SourceMode>(v);
        } else if (key == "sourceRange") {
            if (!r.consume('{')) return false;
            do {
                std::string rk;
                if (!r.parseKey(rk)) return false;
                double dv = 0.0;
                if (!r.parseDouble(dv)) return false;
                if (rk == "startSeconds") s.sourceRange.startSeconds = dv;
                else if (rk == "endSeconds") s.sourceRange.endSeconds = dv;
                else if (rk == "startPpq") s.sourceRange.startPpq = dv;
                else if (rk == "endPpq") s.sourceRange.endPpq = dv;
            } while (r.consume(','));
            if (!r.consume('}')) return false;
        } else if (key == "trackGuids") {
            if (!r.consume('[')) return false;
            r.skipWs();
            if (!r.consume(']')) {
                do {
                    std::string g;
                    if (!r.parseString(g)) return false;
                    s.trackGuids.push_back(g);
                } while (r.consume(','));
                if (!r.consume(']')) return false;
            }
        } else if (key == "wetDry") {
            if (!r.parseDouble(s.wetDry)) return false;
        } else if (key == "channelCount") {
            if (!r.parseInt(s.channelCount)) return false;
        } else if (key == "sampleRate") {
            if (!r.parseInt(s.sampleRate)) return false;
        } else if (key == "lengthSeconds") {
            if (!r.parseDouble(s.lengthSeconds)) return false;
        } else if (key == "lengthBeats") {
            if (!r.parseDouble(s.lengthBeats)) return false;
        } else if (key == "captureTempo") {
            if (!r.parseDouble(s.captureTempo)) return false;
        } else if (key == "captureTimeSigNum") {
            if (!r.parseInt(s.captureTimeSigNum)) return false;
        } else if (key == "captureTimeSigDenom") {
            if (!r.parseInt(s.captureTimeSigDenom)) return false;
        } else if (key == "key") {
            bool wasNull = false;
            if (!r.expectNullOr(wasNull)) return false;
            if (wasNull) {
                s.key.reset();
            } else {
                std::string k;
                if (!r.parseString(k)) return false;
                s.key = k;
            }
        } else if (key == "rootNote") {
            bool wasNull = false;
            if (!r.expectNullOr(wasNull)) return false;
            if (wasNull) {
                s.rootNote.reset();
            } else {
                int v = 0;
                if (!r.parseInt(v)) return false;
                // Valid MIDI note range: 0..127 inclusive (boundaries valid).
                if (v < 0 || v > 127) return false;
                s.rootNote = v;
            }
        } else if (key == "loop") {
            bool wasNull = false;
            if (!r.expectNullOr(wasNull)) return false;
            if (wasNull) {
                s.loop.reset();
            } else {
                if (!r.consume('{')) return false;
                LoopPoints lp;
                do {
                    std::string lk;
                    if (!r.parseKey(lk)) return false;
                    std::int64_t lv = 0;
                    if (!r.parseInt64(lv)) return false;
                    if (lk == "start") lp.start = lv;
                    else if (lk == "end") lp.end = lv;
                } while (r.consume(','));
                if (!r.consume('}')) return false;
                // Invariant: 0 <= start <= end. start == end is a valid zero-length
                // marker; a negative index or start > end is malformed, not silently
                // clamped (mirrors the enum-range rejection above).
                if (lp.start < 0 || lp.end < lp.start) return false;
                s.loop = lp;
            }
        } else if (key == "levels") {
            if (!r.consume('{')) return false;
            do {
                std::string lk;
                if (!r.parseKey(lk)) return false;
                double dv = 0.0;
                if (!r.parseDouble(dv)) return false;
                if (lk == "peakDb") s.levels.peakDb = dv;
                else if (lk == "rmsDb") s.levels.rmsDb = dv;
                else if (lk == "lufs") s.levels.lufs = dv;
            } while (r.consume(','));
            if (!r.consume('}')) return false;
        } else if (key == "clipped") {
            if (!r.parseBool(s.clipped)) return false;
        } else if (key == "tier") {
            int v = 0;
            if (!r.parseInt(v)) return false;
            // Valid range: Scratch(0) .. Archive(1).
            if (v < static_cast<int>(Tier::Scratch) ||
                v > static_cast<int>(Tier::Archive))
                return false;
            s.tier = static_cast<Tier>(v);
        } else if (key == "contentHash") {
            if (!r.parseString(s.contentHash)) return false;
        } else if (key == "provenance") {
            bool wasNull = false;
            if (!r.expectNullOr(wasNull)) return false;
            if (wasNull) {
                s.provenance.reset();
            } else {
                if (!r.consume('{')) return false;
                Provenance p;
                do {
                    std::string pk;
                    if (!r.parseKey(pk)) return false;
                    std::string pv;
                    if (!r.parseString(pv)) return false;
                    if (pk == "parentSampleId") p.parentSampleId = pv;
                    else if (pk == "fxChainSnapshot") p.fxChainSnapshot = pv;
                } while (r.consume(','));
                if (!r.consume('}')) return false;
                s.provenance = p;
            }
        } else if (key == "createdTimestamp") {
            if (!r.parseInt64(s.createdTimestamp)) return false;
        } else {
            if (!r.skipValue()) return false;  // forward-compat: ignore unknown
        }
    } while (r.consume(','));

    return r.consume('}');
}

bool parseIndex(json::Reader& r, BankModel& out) {
    if (!r.consume('{')) return false;
    r.skipWs();
    if (r.consume('}')) return true;  // empty object — vacuously an empty index

    std::vector<Sample> parsed;
    do {
        std::string key;
        if (!r.parseKey(key)) return false;

        if (key == "samples") {
            if (!r.consume('[')) return false;
            r.skipWs();
            if (!r.consume(']')) {
                do {
                    Sample s;
                    if (!parseSample(r, s)) return false;
                    parsed.push_back(std::move(s));
                } while (r.consume(','));
                if (!r.consume(']')) return false;
            }
        } else {
            if (!r.skipValue()) return false;  // version, or unknown keys
        }
    } while (r.consume(','));

    if (!r.consume('}')) return false;

    // Trailing garbage after the root object is malformed.
    r.skipWs();
    if (!r.eof()) return false;

    // Rebuild via add() so the same invariants (relative-path, dedup) that guard
    // live inserts also guard deserialized data. Rejected/collapsed entries are
    // dropped silently — a well-formed serialized index never triggers them.
    for (auto& s : parsed) out.add(s);
    return true;
}

} // namespace

std::optional<BankModel> BankModel::deserialize(const std::string& blob) {
    BankModel idx;
    json::Reader r(blob);
    if (!parseIndex(r, idx)) return std::nullopt;
    return idx;
}

} // namespace reasampler::model
