// Tests for the pure partial/segment stroke eraser (P0 engine layer).

import 'package:badnote/editor/canvas/pen_stroke.dart';
import 'package:badnote/editor/engine/stroke_eraser.dart';
import 'package:flutter_test/flutter_test.dart';

PenStroke _line(List<List<double>> xy) => PenStroke(
      points: [for (final p in xy) PenPoint(p[0], p[1], 0.5)],
      color: 0xFF000000,
      width: 0.006,
      kind: PenStrokeKind.pen,
    );

void main() {
  group('strokeHit (whole-stroke proximity)', () {
    final stroke = _line([
      [0.0, 0.5],
      [0.5, 0.5],
      [1.0, 0.5],
    ]);

    test('hits when a point is inside the radius', () {
      expect(strokeHit(stroke, 0.5, 0.5, 0.05), isTrue);
    });

    test('misses when every point is outside the radius', () {
      expect(strokeHit(stroke, 0.5, 0.9, 0.05), isFalse);
    });
  });

  group('splitStrokeByCircle (partial erase)', () {
    test('no hit returns the SAME stroke object (cheap no-change signal)', () {
      final stroke = _line([
        [0.0, 0.0],
        [0.2, 0.0],
      ]);
      final out = splitStrokeByCircle(stroke, 0.9, 0.9, 0.05);
      expect(out, hasLength(1));
      expect(identical(out.first, stroke), isTrue);
    });

    test('erasing the middle of a straight line splits it into two pieces', () {
      // 5 evenly spaced points along y=0.5; erase the center point only.
      final stroke = _line([
        [0.0, 0.5],
        [0.25, 0.5],
        [0.5, 0.5],
        [0.75, 0.5],
        [1.0, 0.5],
      ]);
      // radius small enough to catch only the x=0.5 point.
      final out = splitStrokeByCircle(stroke, 0.5, 0.5, 0.1);
      expect(out, hasLength(2));
      expect(out[0].points.map((p) => p.x), [0.0, 0.25]);
      expect(out[1].points.map((p) => p.x), [0.75, 1.0]);
    });

    test('erasing every point removes the stroke entirely', () {
      final stroke = _line([
        [0.5, 0.5],
        [0.51, 0.5],
        [0.52, 0.5],
      ]);
      final out = splitStrokeByCircle(stroke, 0.51, 0.5, 0.5);
      expect(out, isEmpty);
    });

    test('orphan single-survivor runs are dropped (no speckle dots)', () {
      // erase points 1 and 3 → survivors are isolated singletons at 0 and 2 and 4.
      final stroke = _line([
        [0.0, 0.5],
        [0.25, 0.5],
        [0.5, 0.5],
        [0.75, 0.5],
        [1.0, 0.5],
      ]);
      // Two tiny erase passes won't fit in one circle; instead verify the
      // single-survivor drop directly: erase the two interior neighbors of a
      // lone point. Use a circle covering x in (0.1..0.9) except the exact
      // center is also covered — so all interior gone, endpoints survive as
      // singletons and must be dropped.
      final out = splitStrokeByCircle(stroke, 0.5, 0.5, 0.45);
      // endpoints 0.0 and 1.0 are >0.45 away in x → survive, but each is a lone
      // point (its neighbor was erased) → both dropped → empty.
      expect(out, isEmpty);
    });

    test('preserves color / width / kind on the cut pieces', () {
      final stroke = PenStroke(
        points: [
          const PenPoint(0.0, 0.5, 0.5),
          const PenPoint(0.25, 0.5, 0.5),
          const PenPoint(0.5, 0.5, 0.5),
          const PenPoint(0.75, 0.5, 0.5),
          const PenPoint(1.0, 0.5, 0.5),
        ],
        color: 0xFFFF0000,
        width: 0.02,
        kind: PenStrokeKind.highlighter,
      );
      final out = splitStrokeByCircle(stroke, 0.5, 0.5, 0.1);
      expect(out, hasLength(2));
      for (final piece in out) {
        expect(piece.color, 0xFFFF0000);
        expect(piece.width, 0.02);
        expect(piece.kind, PenStrokeKind.highlighter);
      }
    });

    test('aspect correction amplifies the y delta', () {
      // A single point offset only in y (dy = 0.2) from the eraser center.
      final stroke = _line([
        [0.5, 0.2],
      ]);
      // aspect 1.0 → dy 0.2 < radius 0.25 → hit.
      expect(strokeHit(stroke, 0.5, 0.0, 0.25, aspect: 1.0), isTrue);
      // aspect 1.5 → effective dy 0.30 > radius 0.25 → miss.
      expect(strokeHit(stroke, 0.5, 0.0, 0.25, aspect: 1.5), isFalse);
    });
  });
}
