// lib/editor/engine/stroke_eraser.dart
//
// Pure stroke-eraser geometry for the BadNote editor (P0 engine layer).
//
// Operates on the live `PenStroke`/`PenPoint` model in NORMALIZED page
// coordinates ([0,1] x [0,1] relative to the page rectangle). It provides two
// erase modes:
//
//   * [strokeHit]            — whole-stroke proximity test (the legacy behavior
//                              that `pen_canvas._eraseAt` used: any point within
//                              the eraser circle ⇒ the entire stroke is removed).
//   * [splitStrokeByCircle]  — partial / segment erase: points inside the eraser
//                              circle are removed, and each maximal run of
//                              surviving consecutive points becomes its own
//                              sub-stroke. A long stroke grazed in the middle is
//                              cut into two pieces instead of vanishing whole.
//
// ASPECT: x and y are each normalized against a different page dimension, so an
// on-screen circular eraser maps to an ELLIPSE in normalized space. Callers pass
// [aspect] = pageHeight / pageWidth so the y delta is corrected and the eraser
// feels round on screen. [aspect] = 1.0 reproduces the legacy (uncorrected,
// width-normalized) distance.

import '../canvas/pen_stroke.dart';

/// Squared, aspect-corrected normalized distance from ([cx],[cy]) to [p].
double _dist2(PenPoint p, double cx, double cy, double aspect) {
  final dx = p.x - cx;
  final dy = (p.y - cy) * aspect;
  return dx * dx + dy * dy;
}

/// True when any point of [stroke] lies within [radius] (normalized, in page-
/// width fractions) of the eraser center ([cx],[cy]). This is the whole-stroke
/// hit test — equivalent to the legacy `_eraseAt` proximity check.
bool strokeHit(
  PenStroke stroke,
  double cx,
  double cy,
  double radius, {
  double aspect = 1.0,
}) {
  final r2 = radius * radius;
  for (final p in stroke.points) {
    if (_dist2(p, cx, cy, aspect) < r2) return true;
  }
  return false;
}

/// Partial erase: remove every point of [stroke] within [radius] of the eraser
/// center ([cx],[cy]) and return the surviving sub-strokes (preserving color /
/// width / kind). Each maximal run of >= 2 consecutive surviving points becomes
/// one sub-stroke; orphaned single survivors are dropped (a 1-point dot left
/// between two erased gaps is visually negligible and avoids speckle).
///
/// Returns:
///   * `[stroke]`  when nothing is erased (no point hit) — same identity, so the
///     caller can cheaply detect "no change".
///   * `[]`        when the whole stroke is erased.
///   * 1+ new strokes otherwise (the cut pieces).
List<PenStroke> splitStrokeByCircle(
  PenStroke stroke,
  double cx,
  double cy,
  double radius, {
  double aspect = 1.0,
}) {
  final r2 = radius * radius;
  final pts = stroke.points;

  // Fast path: if no point is hit, the stroke is unchanged (return same object).
  var anyHit = false;
  for (final p in pts) {
    if (_dist2(p, cx, cy, aspect) < r2) {
      anyHit = true;
      break;
    }
  }
  if (!anyHit) return [stroke];

  final result = <PenStroke>[];
  var run = <PenPoint>[];

  void flush() {
    if (run.length >= 2) {
      result.add(PenStroke(
        points: List.of(run),
        color: stroke.color,
        width: stroke.width,
        kind: stroke.kind,
      ));
    }
    run = <PenPoint>[];
  }

  for (final p in pts) {
    if (_dist2(p, cx, cy, aspect) < r2) {
      flush(); // hit a gap → close the current surviving run
    } else {
      run.add(p);
    }
  }
  flush();

  return result;
}
