// lib/editor/canvas/input_diagnostics.dart
//
// Live zoom/pan diagnostics for the pen canvas. PenInteractiveViewer records one
// entry per scale-update frame; the editor's diagnostic overlay displays the
// accumulated summary + a rolling trace so a SINGLE device session reveals the
// nature of any "跳变" (is it a raw-scale spike, a focal/position jump, or a
// pointer-count oscillation?). All numbers reset via [reset].

import 'package:flutter/foundation.dart';

import '../input/diagnostic_logger.dart';

class InputDiagnostics extends ChangeNotifier {
  InputDiagnostics._();
  static final InputDiagnostics instance = InputDiagnostics._();

  int frames = 0;
  int scaleDropped = 0; // frames rejected as a scale glitch
  int focalDropped = 0; // frames rejected as a focal/position glitch
  int rebaselines = 0; // pointer-count re-baselines
  int pointerCountMax = 0;
  double rawScaleMin = double.infinity, rawScaleMax = 0;
  double scaleMin = double.infinity, scaleMax = 0;
  double maxFocalJumpPx = 0; // largest single-frame local focal delta
  double maxAppliedScaleJump = 1; // largest single-frame applied scale ratio

  final List<String> _trace = <String>[];
  List<String> get trace => List.unmodifiable(_trace);

  void recordRebaseline() {
    rebaselines++;
    DiagnosticLogger.instance.log('ZOOM rebaseline');
    notifyListeners();
  }

  void recordScaleFrame({
    required double rawScale,
    required int pointerCount,
    required double currentScale,
    required double appliedChange, // 1.0 when the frame was dropped
    required double focalJumpPx,
    required bool scaleDrop,
    required bool focalDrop,
  }) {
    frames++;
    if (scaleDrop) scaleDropped++;
    if (focalDrop) focalDropped++;
    if (rawScale < rawScaleMin) rawScaleMin = rawScale;
    if (rawScale > rawScaleMax) rawScaleMax = rawScale;
    final double resulting = currentScale * appliedChange;
    if (resulting < scaleMin) scaleMin = resulting;
    if (resulting > scaleMax) scaleMax = resulting;
    if (pointerCount > pointerCountMax) pointerCountMax = pointerCount;
    if (focalJumpPx > maxFocalJumpPx) maxFocalJumpPx = focalJumpPx;
    final double jump = appliedChange >= 1 ? appliedChange : 1 / appliedChange;
    if (jump > maxAppliedScaleJump) maxAppliedScaleJump = jump;

    final String line = 'p$pointerCount raw=${rawScale.toStringAsFixed(3)} '
        'ch=${appliedChange.toStringAsFixed(3)} '
        'cur=${currentScale.toStringAsFixed(3)} '
        'fj=${focalJumpPx.toStringAsFixed(0)}'
        '${scaleDrop ? " SDROP" : ""}${focalDrop ? " FDROP" : ""}';
    _trace.add(line);
    if (_trace.length > 24) _trace.removeAt(0);
    DiagnosticLogger.instance.log('ZOOM $line');
    notifyListeners();
  }

  void reset() {
    frames = scaleDropped = focalDropped = rebaselines = pointerCountMax = 0;
    rawScaleMin = scaleMin = double.infinity;
    rawScaleMax = scaleMax = 0;
    maxFocalJumpPx = 0;
    maxAppliedScaleJump = 1;
    _trace.clear();
    notifyListeners();
  }

  String _f(double v) => v.isFinite ? v.toStringAsFixed(2) : '-';

  String summary() {
    if (frames == 0) return 'zoom: (pinch to record)';
    return 'zoom f=$frames sDrop=$scaleDropped fDrop=$focalDropped '
        'rebase=$rebaselines pMax=$pointerCountMax\n'
        '  raw=${_f(rawScaleMin)}..${_f(rawScaleMax)} '
        'scale=${_f(scaleMin)}..${_f(scaleMax)}\n'
        '  maxFocalJump=${maxFocalJumpPx.toStringAsFixed(0)}px '
        'maxScaleJump=${_f(maxAppliedScaleJump)}';
  }
}
