// Direct tests for InkPictureCache (the P0.5 perf primitive: a committed-ink
// ui.Picture is recorded once per revision and replayed, so pinch/pan/live-move
// frames never re-rasterize). Verifies build-once caching, revision-keyed
// rebuild, LRU eviction, and post-frame disposal.

import 'dart:ui' as ui;

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:badnote/editor/render/ink_picture_cache.dart';

ui.Picture _pic() {
  final recorder = ui.PictureRecorder();
  Canvas(recorder).drawRect(
    const Rect.fromLTWH(0, 0, 1, 1),
    Paint()..color = const Color(0xFF000000),
  );
  return recorder.endRecording();
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  const size = Size(100, 100);

  test('same (host,revision) builds once, then returns the cached Picture', () {
    final cache = InkPictureCache();
    var builds = 0;
    ui.Picture build() {
      builds++;
      return _pic();
    }

    final p1 = cache.getOrBuild('h', 3, size, build);
    final p2 = cache.getOrBuild('h', 3, size, build);
    expect(builds, 1, reason: 'second call is a cache hit');
    expect(identical(p1, p2), isTrue);
    cache.dispose();
  });

  test('a new revision rebuilds (revision is part of the key)', () {
    final cache = InkPictureCache();
    var builds = 0;
    cache.getOrBuild('h', 1, size, () { builds++; return _pic(); });
    cache.getOrBuild('h', 2, size, () { builds++; return _pic(); });
    expect(builds, 2);
    cache.dispose();
  });

  test('different host ids are independent', () {
    final cache = InkPictureCache();
    var builds = 0;
    cache.getOrBuild('a', 1, size, () { builds++; return _pic(); });
    cache.getOrBuild('b', 1, size, () { builds++; return _pic(); });
    expect(builds, 2);
    cache.dispose();
  });

  testWidgets('evicts + disposes the LRU beyond maxSize (post-frame)',
      (tester) async {
    final cache = InkPictureCache(maxSize: 2);
    final first = cache.getOrBuild('a', 1, const Size(100, 100), _pic);
    cache.getOrBuild('b', 1, const Size(100, 100), _pic);
    // Re-touch 'a' so 'b' becomes LRU.
    cache.getOrBuild('a', 1, const Size(100, 100), _pic);
    // Insert a 3rd → evicts the LRU ('b'); 'a' (first) survives.
    cache.getOrBuild('c', 1, const Size(100, 100), _pic);

    // 'a' is still cached → no rebuild.
    var rebuilt = false;
    final aAgain = cache.getOrBuild(
        'a', 1, const Size(100, 100), () { rebuilt = true; return _pic(); });
    expect(rebuilt, isFalse);
    expect(identical(aAgain, first), isTrue);

    cache.dispose();
    await tester.pump(); // run the deferred disposals
  });
}
