// Tests for stroke spatial bounds + broad-phase visibility (F7 board culling).

import 'dart:ui' show Rect;

import 'package:flutter_test/flutter_test.dart';

import 'package:badnote/editor/engine/stroke_model.dart';
import 'package:badnote/editor/engine/stroke_bounds.dart';

EditorStroke _stroke(List<List<double>> pts) => EditorStroke.create(
      id: 's',
      points: [for (final p in pts) EditorPoint(x: p[0], y: p[1])],
    );

void main() {
  test('strokeBounds is the tight box over points', () {
    final b = strokeBounds(_stroke([
      [0.1, 0.2],
      [0.5, 0.1],
      [0.3, 0.6],
    ]))!;
    expect(b.left, closeTo(0.1, 1e-9));
    expect(b.top, closeTo(0.1, 1e-9));
    expect(b.right, closeTo(0.5, 1e-9));
    expect(b.bottom, closeTo(0.6, 1e-9));
  });

  test('empty stroke → null', () {
    expect(strokeBounds(_stroke([])), isNull);
  });

  test('single point → zero-size rect at that point', () {
    final b = strokeBounds(_stroke([
      [0.4, 0.7],
    ]))!;
    expect(b.width, 0);
    expect(b.height, 0);
    expect(b.left, closeTo(0.4, 1e-9));
    expect(b.top, closeTo(0.7, 1e-9));
  });

  test('strokesBounds is the union; skips empty strokes', () {
    final b = strokesBounds([
      _stroke([
        [0.1, 0.1],
        [0.2, 0.2],
      ]),
      _stroke([]), // skipped
      _stroke([
        [0.8, 0.7],
      ]),
    ])!;
    expect(b.left, closeTo(0.1, 1e-9));
    expect(b.top, closeTo(0.1, 1e-9));
    expect(b.right, closeTo(0.8, 1e-9));
    expect(b.bottom, closeTo(0.7, 1e-9));
  });

  test('strokesBounds of all-empty → null', () {
    expect(strokesBounds([_stroke([]), _stroke([])]), isNull);
  });

  group('strokeIntersects (broad-phase)', () {
    final view = const Rect.fromLTRB(0.0, 0.0, 0.5, 0.5);

    test('inside the viewport intersects', () {
      expect(strokeIntersects(_stroke([
            [0.1, 0.1],
            [0.2, 0.2],
          ]), view), isTrue);
    });

    test('fully outside does NOT intersect', () {
      expect(strokeIntersects(_stroke([
            [0.8, 0.8],
            [0.9, 0.9],
          ]), view), isFalse);
    });

    test('partial overlap intersects', () {
      expect(strokeIntersects(_stroke([
            [0.4, 0.4],
            [0.7, 0.7],
          ]), view), isTrue);
    });

    test('touching edge counts as intersecting', () {
      expect(strokeIntersects(_stroke([
            [0.5, 0.5],
          ]), view), isTrue);
    });

    test('empty stroke is never visible', () {
      expect(strokeIntersects(_stroke([]), view), isFalse);
    });
  });
}
