#include "core/model/slot_map.h"

#include <algorithm>

#include "core/json/json.h"

// slot_map implementation.
//
// The invariant: entries_ is kept sorted ascending by slot, one id per slot, one
// slot per id. Every mutator restores it; queries assume it.

namespace reasampler::model {

void SlotMap::sortBySlot() {
    std::stable_sort(entries_.begin(), entries_.end(),
                     [](const Entry& a, const Entry& b) { return a.slot < b.slot; });
}

int SlotMap::slotOf(const std::string& id) const {
    for (const auto& e : entries_)
        if (e.id == id) return e.slot;
    return -1;
}

std::string SlotMap::idAt(int slot) const {
    for (const auto& e : entries_)
        if (e.slot == slot) return e.id;
    return {};
}

int SlotMap::maxSlot() const {
    int m = -1;
    for (const auto& e : entries_)
        if (e.slot > m) m = e.slot;
    return m;
}

std::vector<std::string> SlotMap::orderedIds() const {
    // entries_ is sorted ascending by slot, so a straight walk is display order.
    std::vector<std::string> out;
    out.reserve(entries_.size());
    for (const auto& e : entries_) out.push_back(e.id);
    return out;
}

void SlotMap::append(const std::string& id) {
    if (id.empty()) return;
    remove(id);                       // an existing id is re-appended, not left in place
    entries_.push_back(Entry{id, maxSlot() + 1});  // next free slot after the last occupied
    sortBySlot();
}

bool SlotMap::remove(const std::string& id) {
    for (auto it = entries_.begin(); it != entries_.end(); ++it) {
        if (it->id == id) {
            entries_.erase(it);       // leaves the slot empty — no re-pack
            return true;
        }
    }
    return false;
}

bool SlotMap::reorder(const std::string& id, int targetSlot) {
    if (slotOf(id) < 0) return false;  // not mapped -> no mutation
    if (targetSlot < 0) targetSlot = 0;
    if (slotOf(id) == targetSlot) return false;  // already there — true no-op

    // Detach the moving id first so the occupancy test below sees the post-move world.
    remove(id);

    const bool occupied = !idAt(targetSlot).empty();
    if (occupied) {
        // Insert-before-and-shift: every occupant at slot >= targetSlot shifts up by one,
        // preserving relative order and interior gaps above the target. The moving id then
        // takes targetSlot cleanly.
        for (auto& e : entries_)
            if (e.slot >= targetSlot) ++e.slot;
    }
    entries_.push_back(Entry{id, targetSlot});
    sortBySlot();
    return true;
}

void SlotMap::resetDense(const std::vector<std::string>& ids) {
    entries_.clear();
    int slot = 0;
    for (const auto& id : ids) {
        if (id.empty()) continue;
        if (slotOf(id) >= 0) continue;  // skip a duplicate id (one slot per id)
        entries_.push_back(Entry{id, slot++});
    }
    // Already ascending by construction; no sort needed.
}

void SlotMap::reconcile(const std::vector<std::string>& liveIds) {
    // Drop markers whose sample left the index.
    entries_.erase(
        std::remove_if(entries_.begin(), entries_.end(),
                       [&](const Entry& e) {
                           return std::find(liveIds.begin(), liveIds.end(), e.id) ==
                                  liveIds.end();
                       }),
        entries_.end());
    // Append live ids that have no mapping yet (out-of-band index growth), in liveIds
    // order, each to the next free slot after the current frontier.
    for (const auto& id : liveIds)
        if (slotOf(id) < 0) append(id);
    sortBySlot();
}

bool SlotMap::operator==(const SlotMap& o) const {
    return entries_ == o.entries_;
}

SlotMap SlotMap::fromEntries(const std::vector<std::pair<std::string, int>>& pairs) {
    SlotMap m;
    for (const auto& [id, slot] : pairs) {
        if (id.empty() || slot < 0) continue;         // drop malformed pair
        if (m.slotOf(id) >= 0) continue;              // duplicate id: first wins
        if (!m.idAt(slot).empty()) continue;          // slot taken: never double-occupy
        m.entries_.push_back(Entry{id, slot});
    }
    m.sortBySlot();
    return m;
}

std::string SlotMap::serialize() const {
    // Array of {id, slot} objects in ascending slot order (entries_ is kept sorted).
    std::string out;
    out += '[';
    for (std::size_t i = 0; i < entries_.size(); ++i) {
        if (i) out += ',';
        json::Writer e(out);
        e.keyStr("id", entries_[i].id);
        e.keyRaw("slot", json::numToStr(entries_[i].slot));
    }
    out += ']';
    return out;
}

}  // namespace reasampler::model
