// lib/editor/engine/stroke_host.dart
//
// A CoordinateSpaceHost (plan principle #2: ONE host-agnostic ink engine). A
// host is anything ink attaches to — a PDF page, an infinite board region, or a
// (P5) CAS overlay — identified by [hostId], with a [contentSize] that defines
// the normalized↔pixel mapping, and a committed [StrokeStore]. The viewport
// mounts one AnnotationLayer per host; nothing in the engine knows whether it's
// a page or a board.
//
// Pure (no widgets/pdfrx); ties together StrokeStore (P0) + stroke_bounds
// broad-phase culling. Unit-tested.

import 'dart:ui' show Rect, Size;

import 'stroke_bounds.dart';
import 'stroke_model.dart';
import 'stroke_store.dart';

/// One ink host: identity + content geometry + its committed strokes.
class StrokeHost {
  StrokeHost({
    required this.hostId,
    required this.contentSize,
    StrokeStore? store,
  }) : store = store ?? StrokeStore();

  /// Stable id (e.g. `"doc:<id>:page:<n>"` or a board-region id) used as the
  /// ink Picture cache key prefix + the persistence host id.
  final String hostId;

  /// Content size in logical px at scale 1; normalized [0,1] coords map onto it.
  final Size contentSize;

  /// Committed strokes for this host (revision-tracked).
  final StrokeStore store;

  /// Revision of the committed strokes (O(1) repaint gate passthrough).
  int get revision => store.revision;

  /// Committed strokes whose bounds overlap [viewportNormalized] — broad-phase
  /// culling for the infinite board (skip off-screen strokes). For a bounded
  /// PDF page the whole page is usually in view, so callers can skip this.
  List<EditorStroke> strokesIn(Rect viewportNormalized) {
    return [
      for (final s in store.committed)
        if (strokeIntersects(s, viewportNormalized)) s,
    ];
  }

  @override
  String toString() => 'StrokeHost($hostId, $contentSize, rev=$revision)';
}
