import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

// Widget builder allows for null
typedef MaybeBuildWidgetType = Widget? Function(BuildContext context);

/// Creates a widget that rebuilds when the given listenable calls
/// notifyListeners() function
class ListenableWidgetBuilder extends StatefulWidget {
  /// The arguments is not required.
  const ListenableWidgetBuilder({
    super.key,
    this.listenable,
    this.builder,
  });

  /// Checked in State object if, in fact, a Listenable
  final dynamic listenable;

  /// Supply a Widget builder
  final MaybeBuildWidgetType? builder;

  /// Subclasses typically do not override this method.
  @override
  State createState() => _ListenableState();

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<Listenable>('listenable', listenable));
  }
}

//
class _ListenableState extends State<ListenableWidgetBuilder> {
  //
  @override
  void initState() {
    super.initState();
    // May actually be a Listenable
    if (widget.builder != null && widget.listenable is Listenable) {
      builder = widget.builder!;
      listenable = widget.listenable;
      listenable?.addListener(_callBuild);
    } else {
      // A widget must be provided. Even if it's nothing
      builder = (_) => const SizedBox.shrink();
    }
  }

  // Possibly not provided
  Listenable? listenable;
  // A Widget will be returned no matter what is provided.
  late MaybeBuildWidgetType builder;
  // Store the past widget displayed, and use it if builder returns null.
  Widget? _widget;

  @override
  void didUpdateWidget(ListenableWidgetBuilder oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Possibly a O(2) expense if oldWidget == widget
    if (oldWidget.listenable is Listenable) {
      oldWidget.listenable?.removeListener(_callBuild);
    }
    if (widget.listenable is Listenable) {
      widget.listenable?.addListener(_callBuild);
    }
  }

  @override
  void dispose() {
    _widget = null;
    listenable?.removeListener(_callBuild);
    listenable = null;
    super.dispose();
  }

  void _callBuild() {
    if (mounted) {
      setState(() {
        // Call the build() function again
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final widget = builder.call(context);
    // The builder is allowed to return null.
    if (widget == null) {
      _widget ??= const SizedBox.shrink();
    } else {
      _widget = widget;
    }
    return _widget!;
  }
}