/// Contract for in-memory and persistent local caching.
///
/// [T] is the type of value stored in the cache. Implementations may back
/// this with an in-memory map, SQLite, shared preferences, or any other
/// storage mechanism appropriate for the platform.
abstract class LocalCache<T> {
  /// Returns the cached value for [key], or `null` if the key is absent or
  /// the entry has expired.
  Future<T?> get(String key);

  /// Stores [value] under [key].
  ///
  /// If [ttl] is provided the entry expires after that duration and subsequent
  /// [get] calls will return `null`.
  Future<void> put(String key, T value, {Duration? ttl});

  /// Removes the entry for [key]. No-op if the key is not present.
  Future<void> remove(String key);

  /// Removes all entries from the cache.
  Future<void> clear();

  /// Returns `true` if [key] is present and has not expired.
  Future<bool> containsKey(String key);

  /// Returns all non-expired keys currently in the cache.
  Future<List<String>> keys();

  /// Releases any resources held by this cache (file handles, DB connections).
  Future<void> dispose();
}

/// A [LocalCache] backed by an in-memory [Map].
///
/// Entries are lost when the process exits. Suitable for short-lived caching
/// within a single app session (e.g. caching API responses for the duration
/// of a screen visit).
class InMemoryCache<T> implements LocalCache<T> {
  final Map<String, _CacheEntry<T>> _store = {};

  @override
  Future<T?> get(String key) async {
    final entry = _store[key];
    if (entry == null) return null;
    if (entry.isExpired) {
      _store.remove(key);
      return null;
    }
    return entry.value;
  }

  @override
  Future<void> put(String key, T value, {Duration? ttl}) async {
    _store[key] = _CacheEntry(
      value: value,
      expiresAt: ttl != null ? DateTime.now().add(ttl) : null,
    );
  }

  @override
  Future<void> remove(String key) async => _store.remove(key);

  @override
  Future<void> clear() async => _store.clear();

  @override
  Future<bool> containsKey(String key) async {
    final entry = _store[key];
    if (entry == null) return false;
    if (entry.isExpired) {
      _store.remove(key);
      return false;
    }
    return true;
  }

  @override
  Future<List<String>> keys() async {
    // Prune expired entries before returning keys.
    _store.removeWhere((_, entry) => entry.isExpired);
    return _store.keys.toList();
  }

  @override
  Future<void> dispose() async => _store.clear();
}

/// Internal entry wrapper that tracks an optional expiry time.
class _CacheEntry<T> {
  final T value;
  final DateTime? expiresAt;

  const _CacheEntry({required this.value, this.expiresAt});

  bool get isExpired => expiresAt != null && DateTime.now().isAfter(expiresAt!);
}
