#include "undo.h"
#include <stdlib.h>
#include <string.h>

void snapshot_stack_init(SnapshotStack *s, int limit) {
    s->items = NULL;
    s->count = 0;
    s->cap = 0;
    s->limit = limit;
}

void snapshot_stack_clear(SnapshotStack *s) {
    for (int i = 0; i < s->count; i++) document_free(&s->items[i].doc);
    s->count = 0;
}

void snapshot_stack_free(SnapshotStack *s) {
    snapshot_stack_clear(s);
    free(s->items);
    s->items = NULL;
    s->cap = 0;
}

void snapshot_stack_push(SnapshotStack *s, const Document *doc, int cursorBlock, int cursorOffset) {
    if (s->count == s->cap) {
        int newCap = s->cap > 0 ? s->cap * 2 : 8;
        s->items = realloc(s->items, (size_t)newCap * sizeof(Snapshot));
        s->cap = newCap;
    }
    Snapshot *slot = &s->items[s->count++];
    document_clone(&slot->doc, doc);
    slot->cursorBlock = cursorBlock;
    slot->cursorOffset = cursorOffset;

    if (s->count > s->limit) {
        /* Evict the oldest entry, shifting the rest down. */
        document_free(&s->items[0].doc);
        memmove(&s->items[0], &s->items[1], (size_t)(s->count - 1) * sizeof(Snapshot));
        s->count--;
    }
}

bool snapshot_stack_pop(SnapshotStack *s, Snapshot *out) {
    if (s->count == 0) return false;
    *out = s->items[--s->count];
    return true;
}

void undo_manager_init(UndoManager *m, int limit) {
    snapshot_stack_init(&m->undo, limit);
    snapshot_stack_init(&m->redo, limit);
    m->lastKind = EDIT_NONE;
    m->lastBlock = -1;
    m->lastOffset = -1;
}

void undo_manager_free(UndoManager *m) {
    snapshot_stack_free(&m->undo);
    snapshot_stack_free(&m->redo);
}

void undo_manager_reset(UndoManager *m) {
    snapshot_stack_clear(&m->undo);
    snapshot_stack_clear(&m->redo);
    m->lastKind = EDIT_NONE;
    m->lastBlock = -1;
    m->lastOffset = -1;
}

void undo_manager_before_edit(UndoManager *m, EditKind kind, const Document *doc, int cursorBlock, int cursorOffset) {
    bool coalesce = (kind == m->lastKind) && (cursorBlock == m->lastBlock) && (cursorOffset == m->lastOffset);
    if (!coalesce) {
        snapshot_stack_push(&m->undo, doc, cursorBlock, cursorOffset);
        snapshot_stack_clear(&m->redo);
    }
}

void undo_manager_after_edit(UndoManager *m, EditKind kind, int cursorBlock, int cursorOffset) {
    m->lastKind = kind;
    m->lastBlock = cursorBlock;
    m->lastOffset = cursorOffset;
}
