// lib/editor/engine/undo_stack.dart
//
// Generic undo/redo stack with a fixed capacity.
//
// RECORD DISCIPLINE: call `record(currentState)` BEFORE applying a mutation.
// The stack saves the pre-mutation snapshot so that `undo` can restore it.
//
// Example:
//   final stack = UndoStack<List<PenStroke>>(cap: 50);
//   // User draws a stroke:
//   stack.record(List.unmodifiable(strokes)); // snapshot before mutation
//   strokes = [...strokes, newStroke];        // apply mutation
//
// Snapshots are treated as opaque, immutable values; the caller is responsible
// for passing copies/immutable lists rather than mutable references.

/// A capped undo/redo stack for arbitrary snapshot types.
///
/// Capacity defaults to 50 entries. When the cap is reached the oldest
/// undo snapshot is silently dropped to make room.
class UndoStack<T> {
  UndoStack({int cap = 50}) : _cap = cap;

  final int _cap;

  // Index 0 = oldest, last = most-recent snapshot available for undo.
  final List<T> _undoStack = [];
  final List<T> _redoStack = [];

  /// True when there is at least one snapshot that can be undone.
  bool get canUndo => _undoStack.isNotEmpty;

  /// True when there is at least one snapshot that can be redone.
  bool get canRedo => _redoStack.isNotEmpty;

  /// Save [snapshot] (the state BEFORE a mutation) onto the undo stack and
  /// clear the redo stack (any branched future is discarded).
  ///
  /// If the stack is at capacity the oldest snapshot is dropped.
  void record(T snapshot) {
    if (_undoStack.length >= _cap) {
      _undoStack.removeAt(0);
    }
    _undoStack.add(snapshot);
    _redoStack.clear();
  }

  /// Undo the last recorded mutation.
  ///
  /// Returns the snapshot to restore, pushing [current] (the live state at
  /// the moment of calling) onto the redo stack. Returns `null` if [canUndo]
  /// is false.
  T? undo(T current) {
    if (!canUndo) return null;
    _redoStack.add(current);
    return _undoStack.removeLast();
  }

  /// Redo the last undone mutation.
  ///
  /// Returns the snapshot to restore and pushes it back onto the undo stack
  /// so it can be undone again. Returns `null` if [canRedo] is false.
  T? redo() {
    if (!canRedo) return null;
    final snapshot = _redoStack.removeLast();
    _undoStack.add(snapshot);
    return snapshot;
  }

  /// Clear both stacks.
  void clear() {
    _undoStack.clear();
    _redoStack.clear();
  }
}
