import 'package:flutter/material.dart';

import 'app_progress_indicator.dart';

/// Builds [builder]'s subtree only while the enclosing route is at rest — i.e.
/// neither its own enter/exit animation nor its secondary (route-pushed-on-top)
/// transition is running.
///
/// Some widgets (notably `SfPdfViewer`) call `localToGlobal` during layout and
/// crash with `RenderBox was not laid out` when an ancestor page-transition
/// `RenderTransform` is mid-first-layout. Those transforms are inserted freshly
/// whenever a transition *starts* — not only on the initial push, but also on
/// pop and when another page is pushed on top. The gate therefore swaps the
/// subtree for [placeholder] for the duration of any transition; the status
/// listener fires before that frame's layout, so the fragile subtree is gone
/// before the new transform lays out.
class RouteTransitionGate extends StatefulWidget {
  const RouteTransitionGate({super.key, required this.builder, this.placeholder});

  final WidgetBuilder builder;

  /// Shown while the route is transitioning. Defaults to a centered large
  /// progress indicator.
  final Widget? placeholder;

  @override
  State<RouteTransitionGate> createState() => _RouteTransitionGateState();
}

class _RouteTransitionGateState extends State<RouteTransitionGate> {
  Animation<double>? _animation;
  Animation<double>? _secondaryAnimation;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final route = ModalRoute.of(context);
    _swapListener(route?.animation, _animation, (a) => _animation = a);
    _swapListener(
      route?.secondaryAnimation,
      _secondaryAnimation,
      (a) => _secondaryAnimation = a,
    );
  }

  void _swapListener(
    Animation<double>? next,
    Animation<double>? current,
    void Function(Animation<double>?) assign,
  ) {
    if (identical(next, current)) return;
    current?.removeStatusListener(_onAnimationStatus);
    assign(next?..addStatusListener(_onAnimationStatus));
  }

  @override
  void dispose() {
    _animation?.removeStatusListener(_onAnimationStatus);
    _secondaryAnimation?.removeStatusListener(_onAnimationStatus);
    super.dispose();
  }

  bool get _transitioning =>
      _isAnimating(_animation?.status) ||
      _isAnimating(_secondaryAnimation?.status);

  static bool _isAnimating(AnimationStatus? status) =>
      status == AnimationStatus.forward || status == AnimationStatus.reverse;

  void _onAnimationStatus(AnimationStatus status) {
    if (mounted) setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    if (_transitioning) {
      return widget.placeholder ??
          const Center(child: AppProgressIndicator.large());
    }
    return widget.builder(context);
  }
}
