import 'dart:collection';

import 'sheet.dart';

/// Types of changes that can be queued for server synchronization.
enum ChangeType {
  sheetNameChange,
  composerNameChange,
  addTagChange,
  removeTagChange,
}

/// Represents a single pending change to be synced with the server.
///
/// Changes are stored locally when offline and applied once
/// the device regains connectivity. Each change has a [createdAt] timestamp
/// that the server uses to resolve conflicts between devices.
class Change {
  final ChangeType type;
  final String sheetUuid;
  final String value;
  final DateTime createdAt;

  Change({
    required this.type,
    required this.sheetUuid,
    required this.value,
    DateTime? createdAt,
  }) : createdAt = createdAt ?? DateTime.now();

  /// Serializes this change to a map for storage.
  Map<String, dynamic> toMap() => {
    'type': type.index,
    'sheetUuid': sheetUuid,
    'value': value,
    'createdAt': createdAt.toIso8601String(),
  };

  /// Serializes this change to JSON for API requests.
  Map<String, dynamic> toJson() => {
    'type': type.name,
    'sheetUuid': sheetUuid,
    'value': value,
    'createdAt': createdAt.toIso8601String(),
  };

  /// Deserializes a change from a stored map.
  ///
  /// Note: Adding new [ChangeType] values may cause issues with
  /// previously stored changes that use index-based serialization.
  factory Change.fromMap(Map<dynamic, dynamic> map) {
    return Change(
      type: ChangeType.values[map['type']],
      sheetUuid: map['sheetUuid'],
      value: map['value'],
      createdAt: map['createdAt'] != null
          ? DateTime.parse(map['createdAt'])
          : DateTime.now(),
    );
  }
}

/// A queue of pending changes to be synchronized with the server.
///
/// Changes are stored in FIFO order (oldest first) and applied
/// to sheets in sequence when syncing.
class ChangeQueue {
  final Queue<Change> _queue = Queue();

  ChangeQueue();

  /// Adds a change to the end of the queue.
  void addChange(Change change) {
    _queue.addLast(change);
  }

  /// Returns the number of pending changes.
  int get length => _queue.length;

  /// Whether the queue has any pending changes.
  bool get isEmpty => _queue.isEmpty;

  /// Whether the queue has pending changes.
  bool get isNotEmpty => _queue.isNotEmpty;

  /// Applies all queued changes to the provided list of sheets.
  ///
  /// Each change modifies the corresponding sheet's properties
  /// based on the change type.
  void applyToSheets(List<Sheet> sheets) {
    for (final change in _queue) {
      final sheet = sheets.firstWhere(
        (s) => s.uuid == change.sheetUuid,
        orElse: () =>
            throw StateError('Sheet with UUID ${change.sheetUuid} not found'),
      );

      switch (change.type) {
        case ChangeType.sheetNameChange:
          sheet.name = change.value;
        case ChangeType.composerNameChange:
          sheet.composer = change.value;
        case ChangeType.addTagChange:
          throw UnimplementedError('Tag support not yet implemented');
        case ChangeType.removeTagChange:
          throw UnimplementedError('Tag support not yet implemented');
      }
    }
  }
}
