// R7 regression (P0 step 7/8): the on-screen painter and the PDF export must
// build their freehand outline from ONE shared recipe. The export bug was
// pdf_service hardcoding `thinning: 0.7, streamline: 0.5` while the screen used
// 0.85 / 0.32 — a hairline mismatch. These tests pin the single source so a
// future edit to one side can't silently re-diverge.

import 'dart:ui';

import 'package:flutter_test/flutter_test.dart';
import 'package:perfect_freehand/perfect_freehand.dart' as pf;

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

void main() {
  const size = Size(800, 600);

  EditorStroke pen() => EditorStroke.create(
        id: 'a',
        points: const [
          EditorPoint(x: 0.1, y: 0.1, pressure: 0.9),
          EditorPoint(x: 0.4, y: 0.2, pressure: 0.5),
          EditorPoint(x: 0.7, y: 0.15, pressure: 0.2),
        ],
        width: 0.01,
      );

  test('buildStrokeOutline traces exactly the shared freehandOutlinePoints', () {
    final stroke = pen();
    final pfPoints = stroke.points
        .map((p) => pf.PointVector(
            p.x * size.width, p.y * size.height, p.pressure ?? 0.5))
        .toList();
    final shared = freehandOutlinePoints(
      pfPoints: pfPoints,
      size: stroke.width * size.width,
      isHighlighter: false,
      hasRealPressure: true,
      isComplete: true,
    );
    expect(shared, isNotEmpty);

    final ref = Path()..moveTo(shared.first.dx, shared.first.dy);
    for (var i = 1; i < shared.length; i++) {
      ref.lineTo(shared[i].dx, shared[i].dy);
    }
    ref.close();

    final screen = buildStrokeOutline(stroke, size, isComplete: true);
    // Same vertices ⇒ identical bounds. (If the screen path used different
    // options than the shared core, the outline would differ.)
    expect(screen.getBounds(), ref.getBounds());
  });

  test('default thinning IS kDefaultPenThinning (export passes the default)', () {
    final pts = [
      pf.PointVector(10, 10, 0.9),
      pf.PointVector(100, 40, 0.5),
      pf.PointVector(200, 30, 0.2),
    ];
    final byDefault = freehandOutlinePoints(
      pfPoints: pts,
      size: 8,
      isHighlighter: false,
      hasRealPressure: true,
      isComplete: true,
    );
    final explicit = freehandOutlinePoints(
      pfPoints: pts,
      size: 8,
      isHighlighter: false,
      hasRealPressure: true,
      isComplete: true,
      thinning: kDefaultPenThinning,
    );
    expect(byDefault, explicit);
  });

  test('thinning actually changes the outline (param is wired, not ignored)',
      () {
    final pts = [
      pf.PointVector(10, 10, 0.9),
      pf.PointVector(100, 40, 0.5),
      pf.PointVector(200, 30, 0.2),
    ];
    final strong = freehandOutlinePoints(
      pfPoints: pts,
      size: 8,
      isHighlighter: false,
      hasRealPressure: true,
      isComplete: true,
      thinning: kDefaultPenThinning,
    );
    final none = freehandOutlinePoints(
      pfPoints: pts,
      size: 8,
      isHighlighter: false,
      hasRealPressure: true,
      isComplete: true,
      thinning: 0.0,
    );
    expect(strong, isNot(equals(none)));
  });

  test('empty input yields an empty outline (no crash)', () {
    expect(
      freehandOutlinePoints(
        pfPoints: const [],
        size: 8,
        isHighlighter: false,
        hasRealPressure: false,
        isComplete: true,
      ),
      isEmpty,
    );
  });
}
