// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// @docImport 'package:flutter/material.dart';
/// @docImport 'package:flutter/scheduler.dart';
library;

import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';

// Examples can assume:
// late BuildContext context;
// late Set<WidgetState> states;

/// This class allows [WidgetState] enum values to be combined
/// using [WidgetStateOperators].
///
/// A [Map] with [WidgetStatesConstraint] objects as keys can be used
/// in the [WidgetStateProperty.fromMap] constructor to resolve to
/// one of its values, based on the first key that [isSatisfiedBy]
/// the current set of states.
///
/// {@macro flutter.widgets.WidgetStateMap}
abstract interface class WidgetStatesConstraint {
  /// Whether the provided [states] satisfy this object's criteria.
  ///
  /// If the constraint is a single [WidgetState] object,
  /// it's satisfied by the set if the set contains the object.
  ///
  /// The constraint can also be created using one or more operators, for example:
  ///
  /// {@template flutter.widgets.WidgetStatesConstraint.isSatisfiedBy}
  /// ```dart
  /// final WidgetStatesConstraint constraint = WidgetState.focused | WidgetState.hovered;
  /// ```
  ///
  /// In the above case, `constraint.isSatisfiedBy(states)` is equivalent to:
  ///
  /// ```dart
  /// states.contains(WidgetState.focused) || states.contains(WidgetState.hovered);
  /// ```
  /// {@endtemplate}
  bool isSatisfiedBy(Set<WidgetState> states);
}

@immutable
sealed class _WidgetStateCombo implements WidgetStatesConstraint {
  const _WidgetStateCombo(this.first, this.second);

  final WidgetStatesConstraint first;
  final WidgetStatesConstraint second;

  @override
  // ignore: hash_and_equals, since == is defined in subclasses
  int get hashCode => Object.hash(first, second);
}

class _WidgetStateAnd extends _WidgetStateCombo {
  const _WidgetStateAnd(super.first, super.second);

  @override
  bool isSatisfiedBy(Set<WidgetState> states) {
    return first.isSatisfiedBy(states) && second.isSatisfiedBy(states);
  }

  @override
  // ignore: hash_and_equals, hashCode is defined in the sealed super-class
  bool operator ==(Object other) {
    return other is _WidgetStateAnd && other.first == first && other.second == second;
  }

  @override
  String toString() => '($first & $second)';
}

class _WidgetStateOr extends _WidgetStateCombo {
  const _WidgetStateOr(super.first, super.second);

  @override
  bool isSatisfiedBy(Set<WidgetState> states) {
    return first.isSatisfiedBy(states) || second.isSatisfiedBy(states);
  }

  @override
  // ignore: hash_and_equals, hashCode is defined in the sealed super-class
  bool operator ==(Object other) {
    return other is _WidgetStateOr && other.first == first && other.second == second;
  }

  @override
  String toString() => '($first | $second)';
}

@immutable
class _WidgetStateNot implements WidgetStatesConstraint {
  const _WidgetStateNot(this.value);

  final WidgetStatesConstraint value;

  @override
  bool isSatisfiedBy(Set<WidgetState> states) => !value.isSatisfiedBy(states);

  @override
  bool operator ==(Object other) {
    return other is _WidgetStateNot && other.value == value;
  }

  @override
  int get hashCode => value.hashCode;

  @override
  String toString() => '~$value';
}

/// These operators can be used inside a [WidgetStateMap] to combine states
/// and find a match.
///
/// Example:
///
/// {@macro flutter.widgets.WidgetStatesConstraint.isSatisfiedBy}
///
/// Since enums can't extend other classes, [WidgetState] instead `implements`
/// the [WidgetStatesConstraint] interface. This `extension` ensures that
/// the operators can be used without being directly inherited.
extension WidgetStateOperators on WidgetStatesConstraint {
  /// Combines two [WidgetStatesConstraint] values using logical "and".
  WidgetStatesConstraint operator &(WidgetStatesConstraint other) => _WidgetStateAnd(this, other);

  /// Combines two [WidgetStatesConstraint] values using logical "or".
  WidgetStatesConstraint operator |(WidgetStatesConstraint other) => _WidgetStateOr(this, other);

  /// Takes a [WidgetStatesConstraint] and applies the logical "not".
  WidgetStatesConstraint operator ~() => _WidgetStateNot(this);
}

// A private class, used to create [WidgetState.any].
class _AnyWidgetStates implements WidgetStatesConstraint {
  const _AnyWidgetStates();

  @override
  bool isSatisfiedBy(Set<WidgetState> states) => true;

  @override
  String toString() => 'WidgetState.any';
}

/// Interactive states that some of the widgets can take on when receiving input
/// from the user.
///
/// States are defined by https://m3.material.io/foundations/interaction/states,
/// but are not limited to the Material design system or library.
///
/// Some widgets track their current state in a `Set<WidgetState>`.
///
/// See also:
///
///  * [MaterialState], the Material specific version of `WidgetState`.
///  * [WidgetStateProperty], an interface for objects that "resolve" to
///    different values depending on a widget's state.
/// {@template flutter.widgets.WidgetStateProperty.implementations}
///  * [WidgetStateColor], a [Color] that implements `WidgetStateProperty`
///    which is used in APIs that need to accept either a [Color] or a
///    `WidgetStateProperty<Color>`.
///  * [WidgetStateMouseCursor], a [MouseCursor] that implements
///    `WidgetStateProperty` which is used in APIs that need to accept either
///    a [MouseCursor] or a [WidgetStateProperty<MouseCursor>].
///  * [WidgetStateOutlinedBorder], an [OutlinedBorder] that implements
///    `WidgetStateProperty` which is used in APIs that need to accept either
///    an [OutlinedBorder] or a [WidgetStateProperty<OutlinedBorder>].
///  * [WidgetStateBorderSide], a [BorderSide] that implements
///    `WidgetStateProperty` which is used in APIs that need to accept either
///    a [BorderSide] or a [WidgetStateProperty<BorderSide>].
///  * [WidgetStateTextStyle], a [TextStyle] that implements
///    `WidgetStateProperty` which is used in APIs that need to accept either
///    a [TextStyle] or a [WidgetStateProperty<TextStyle>].
/// {@endtemplate}
enum WidgetState implements WidgetStatesConstraint {
  /// The state when the user drags their mouse cursor over the given widget.
  ///
  /// See: https://material.io/design/interaction/states.html#hover.
  hovered,

  /// The state when the user navigates with the keyboard to a given widget.
  ///
  /// This can also sometimes be triggered when a widget is tapped. For example,
  /// when a [TextField] is tapped, it becomes [focused].
  ///
  /// See: https://material.io/design/interaction/states.html#focus.
  focused,

  /// The state when the user is actively pressing down on the given widget.
  ///
  /// See: https://material.io/design/interaction/states.html#pressed.
  pressed,

  /// The state when this widget is being dragged from one place to another by
  /// the user.
  ///
  /// https://material.io/design/interaction/states.html#dragged.
  dragged,

  /// The state when this item has been selected.
  ///
  /// This applies to things that can be toggled (such as chips and checkboxes)
  /// and things that are selected from a set of options (such as tabs and radio buttons).
  ///
  /// See: https://material.io/design/interaction/states.html#selected.
  selected,

  /// The state when this widget overlaps the content of a scrollable below.
  ///
  /// Used by [AppBar] to indicate that the primary scrollable's
  /// content has scrolled up and behind the app bar.
  scrolledUnder,

  /// The state when this widget is disabled and cannot be interacted with.
  ///
  /// Disabled widgets should not respond to hover, focus, press, or drag
  /// interactions.
  ///
  /// See: https://material.io/design/interaction/states.html#disabled.
  disabled,

  /// The state when the widget has entered some form of invalid state.
  ///
  /// See https://material.io/design/interaction/states.html#usage.
  error;

  /// {@template flutter.widgets.WidgetState.any}
  /// To prevent a situation where each [WidgetStatesConstraint]
  /// isn't satisfied by the given set of states, consider adding
  /// [WidgetState.any] as the final [WidgetStateMap] key.
  /// {@endtemplate}
  static const WidgetStatesConstraint any = _AnyWidgetStates();

  @override
  bool isSatisfiedBy(Set<WidgetState> states) => states.contains(this);
}

/// Signature for the function that returns a value of type `T` based on a given
/// set of states.
typedef WidgetPropertyResolver<T> = T Function(Set<WidgetState> states);

/// Defines a [Color] that is also a [WidgetStateProperty].
///
/// This class exists to enable widgets with [Color] valued properties
/// to also accept [WidgetStateProperty<Color>] values. A widget
/// state color property represents a color which depends on
/// a widget's "interactive state". This state is represented as a
/// [Set] of [WidgetState]s, like [WidgetState.pressed],
/// [WidgetState.focused] and [WidgetState.hovered].
///
/// [WidgetStateColor] should only be used with widgets that document
/// their support, like [TimePickerThemeData.dayPeriodColor].
///
/// A [WidgetStateColor] can be created in one of the following ways:
///   1. Create a subclass of [WidgetStateColor] and implement the abstract `resolve` method.
///   2. Use [WidgetStateColor.resolveWith] and pass in a callback that
///      will be used to resolve the color in the given states.
///   3. Use [WidgetStateColor.fromMap] to assign a value using a [WidgetStateMap].
///
/// {@tool snippet}
///
/// This example defines a [WidgetStateColor] with a const constructor.
///
/// ```dart
/// class MyColor extends WidgetStateColor {
///   const MyColor() : super(_defaultColor);
///
///   static const int _defaultColor = 0xcafefeed;
///   static const int _pressedColor = 0xdeadbeef;
///
///   @override
///   Color resolve(Set<WidgetState> states) {
///     if (states.contains(WidgetState.pressed)) {
///       return const Color(_pressedColor);
///     }
///     return const Color(_defaultColor);
///   }
/// }
/// ```
/// {@end-tool}
abstract class WidgetStateColor extends Color implements WidgetStateProperty<Color> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const WidgetStateColor(super.defaultValue);

  /// Creates a [WidgetStateColor] from a [WidgetPropertyResolver<Color>]
  /// callback function.
  ///
  /// If used as a regular color, the color resolved in the default state (the
  /// empty set of states) will be used.
  ///
  /// The given callback parameter must return a non-null color in the default
  /// state.
  factory WidgetStateColor.resolveWith(WidgetPropertyResolver<Color> callback) = _WidgetStateColor;

  /// Creates a [WidgetStateColor] from a [WidgetStateMap<Color>].
  ///
  /// {@macro flutter.widgets.WidgetStateProperty.fromMap}
  /// It should only be used with widgets that document support for
  /// [WidgetStateColor] (throws an error if used as a normal [Color]).
  ///
  /// {@macro flutter.widgets.WidgetState.any}
  const factory WidgetStateColor.fromMap(WidgetStateMap<Color> map) = _WidgetStateColorMapper;

  /// Returns a [Color] that's to be used when a component is in the specified
  /// state.
  @override
  Color resolve(Set<WidgetState> states);

  /// A constant whose value is transparent for all states.
  static const WidgetStateColor transparent = _WidgetStateColorTransparent();
}

class _WidgetStateColor extends WidgetStateColor {
  _WidgetStateColor(this._resolve) : super(_resolve(_defaultStates).value);

  final WidgetPropertyResolver<Color> _resolve;

  static const Set<WidgetState> _defaultStates = <WidgetState>{};

  @override
  Color resolve(Set<WidgetState> states) => _resolve(states);
}

class _WidgetStateColorTransparent extends WidgetStateColor {
  const _WidgetStateColorTransparent() : super(0x00000000);

  @override
  Color resolve(Set<WidgetState> states) => const Color(0x00000000);
}

class _WidgetStateColorMapper extends WidgetStateMapper<Color> implements WidgetStateColor {
  const _WidgetStateColorMapper(super.map);
}

/// Defines a [MouseCursor] whose value depends on a set of [WidgetState]s which
/// represent the interactive state of a component.
///
/// This kind of [MouseCursor] is useful when the set of interactive
/// actions a widget supports varies with its state. For example, a
/// mouse pointer hovering over a disabled [ListTile] should not
/// display [SystemMouseCursors.click], since a disabled list tile
/// doesn't respond to mouse clicks. [ListTile]'s default mouse cursor
/// is a [WidgetStateMouseCursor.clickable], which resolves to
/// [SystemMouseCursors.basic] when the button is disabled.
///
/// This class should only be used for parameters that document their support
/// for [WidgetStateMouseCursor].
///
/// A [WidgetStateMouseCursor] can be created in one of the following ways:
///   1. Create a subclass of [WidgetStateMouseCursor] and implement
///      the abstract `resolve` method.
///   2. Use [WidgetStateMouseCursor.resolveWith] and pass in a callback that
///      will be used to resolve the color in the given states.
///   3. Use [WidgetStateMouseCursor.fromMap] to assign a value using a [WidgetStateMap].
///
/// {@tool dartpad}
/// This example defines a mouse cursor that resolves to
/// [SystemMouseCursors.forbidden] when its widget is disabled.
///
/// ** See code in examples/api/lib/widgets/widget_state/widget_state_mouse_cursor.0.dart **
/// {@end-tool}
///
/// See also:
///
///  * [MouseCursor] for introduction on the mouse cursor system.
///  * [SystemMouseCursors], which defines cursors that are supported by
///    native platforms.
abstract class WidgetStateMouseCursor extends MouseCursor
    implements WidgetStateProperty<MouseCursor> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const WidgetStateMouseCursor();

  /// Creates a [WidgetStateMouseCursor] using a [WidgetPropertyResolver]
  /// callback.
  ///
  /// A [debugDescription] may optionally be provided.
  ///
  /// If used as a regular [MouseCursor], the cursor resolved
  /// in the default state (the empty set of states) will be used.
  const factory WidgetStateMouseCursor.resolveWith(
    WidgetPropertyResolver<MouseCursor> callback, {
    String debugDescription,
  }) = _WidgetStateMouseCursor;

  /// Creates a [WidgetStateMouseCursor] from a [WidgetStateMap].
  ///
  /// {@macro flutter.widgets.WidgetStateProperty.fromMap}
  /// It should only be used with classes that document support for
  /// [WidgetStateMouseCursor] (throws an error if used as a regular
  /// [MouseCursor].)
  const factory WidgetStateMouseCursor.fromMap(WidgetStateMap<MouseCursor> map) =
      _WidgetMouseCursorMapper;

  @protected
  @override
  MouseCursorSession createSession(int device) {
    return resolve(const <WidgetState>{}).createSession(device);
  }

  /// Returns a [MouseCursor] that's to be used when a component is in the
  /// specified state.
  @override
  MouseCursor resolve(Set<WidgetState> states);

  /// A mouse cursor for clickable widgets, which resolves differently when the
  /// widget is disabled.
  ///
  /// By default this cursor resolves to [SystemMouseCursors.click]. If the widget is
  /// disabled, the cursor resolves to [SystemMouseCursors.basic].
  static const WidgetStateMouseCursor clickable = WidgetStateMouseCursor.resolveWith(
    _clickable,
    debugDescription: 'WidgetStateMouseCursor(clickable)',
  );

  static MouseCursor _clickable(Set<WidgetState> states) {
    if (states.contains(WidgetState.disabled)) {
      return SystemMouseCursors.basic;
    }
    return SystemMouseCursors.click;
  }

  /// A platform-adaptive mouse cursor for clickable widgets, which resolves
  /// differently based on the widget's state and the platform.
  ///
  /// On web platforms, this cursor resolves to [SystemMouseCursors.click] by
  /// default. If the widget is disabled, it resolves to
  /// [SystemMouseCursors.basic].
  ///
  /// On non-web platforms, this cursor always resolves to
  /// [SystemMouseCursors.basic].
  ///
  /// This cursor is commonly used for interactive widgets like buttons. The
  /// difference in behavior across platforms reflects native conventions (e.g.,
  /// web uses a hand pointer for buttons, while desktop platforms do not).
  static const WidgetStateMouseCursor adaptiveClickable = WidgetStateMouseCursor.resolveWith(
    _adaptiveClickable,
    debugDescription: 'WidgetStateMouseCursor(adaptiveClickable)',
  );

  static MouseCursor _adaptiveClickable(Set<WidgetState> states) {
    if (states.contains(WidgetState.disabled)) {
      return SystemMouseCursors.basic;
    }
    return kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic;
  }

  /// A mouse cursor for widgets related to text, which resolves differently
  /// when the widget is disabled.
  ///
  /// By default this cursor resolves to [SystemMouseCursors.text]. If the widget is
  /// disabled, the cursor resolves to [SystemMouseCursors.basic].
  ///
  /// This cursor is the default for many widgets.
  static const WidgetStateMouseCursor textable = WidgetStateMouseCursor.resolveWith(
    _textable,
    debugDescription: 'WidgetStateMouseCursor(textable)',
  );
  static MouseCursor _textable(Set<WidgetState> states) {
    if (states.contains(WidgetState.disabled)) {
      return SystemMouseCursors.basic;
    }
    return SystemMouseCursors.text;
  }
}

class _WidgetStateMouseCursor extends WidgetStateMouseCursor {
  const _WidgetStateMouseCursor(
    this._resolve, {
    this.debugDescription = 'WidgetStateMouseCursor()',
  });

  final WidgetPropertyResolver<MouseCursor> _resolve;

  @override
  MouseCursor resolve(Set<WidgetState> states) => _resolve(states);

  @override
  final String debugDescription;
}

class _WidgetMouseCursorMapper extends WidgetStateMapper<MouseCursor>
    implements WidgetStateMouseCursor {
  const _WidgetMouseCursorMapper(super.map);
}

/// Defines a [BorderSide] whose value depends on a set of [WidgetState]s
/// which represent the interactive state of a component.
///
/// This class enables existing widget implementations with [BorderSide]
/// properties to be extended to also effectively support `WidgetStateProperty<BorderSide>`
/// property values. It should only be used for parameters that document support
/// for [WidgetStateBorderSide] objects.
///
/// A [WidgetStateBorderSide] can be created in one of the following ways:
///   1. Create a subclass of [WidgetStateBorderSide] and implement the abstract `resolve` method.
///   2. Use [WidgetStateBorderSide.resolveWith] and pass in a callback that
///      will be used to resolve the color in the given states.
///   3. Use [WidgetStateBorderSide.fromMap] to assign a value using a [WidgetStateMap].
///
/// {@tool dartpad}
/// This example defines a [WidgetStateBorderSide] which resolves to different
/// border colors depending on how the user interacts with it.
///
/// ** See code in examples/api/lib/widgets/widget_state/widget_state_border_side.0.dart **
/// {@end-tool}
abstract class WidgetStateBorderSide extends BorderSide
    implements WidgetStateProperty<BorderSide?> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const WidgetStateBorderSide();

  /// Creates a [WidgetStateBorderSide] from a
  /// [WidgetPropertyResolver<BorderSide?>] callback function.
  ///
  /// If used as a regular [BorderSide], its behavior matches an empty
  /// `BorderSide()` constructor.
  ///
  /// Usage:
  ///
  /// ```dart
  /// ChipTheme(
  ///   data: Theme.of(context).chipTheme.copyWith(
  ///     side: WidgetStateBorderSide.resolveWith((Set<WidgetState> states) {
  ///       if (states.contains(WidgetState.selected)) {
  ///         return const BorderSide(color: Colors.red);
  ///       }
  ///       return null;  // Defer to default value on the theme or widget.
  ///     }),
  ///   ),
  ///   child: const Chip(
  ///     label: Text('Transceiver'),
  ///   ),
  /// ),
  /// ```
  ///
  /// Alternatively:
  ///
  /// ```dart
  /// Chip(
  ///   label: const Text('Transceiver'),
  ///   side: WidgetStateBorderSide.resolveWith((Set<WidgetState> states) {
  ///     if (states.contains(WidgetState.selected)) {
  ///       return const BorderSide(color: Colors.red);
  ///     }
  ///     return null;  // Defer to default value on the theme or widget.
  ///   }),
  /// ),
  /// ```
  const factory WidgetStateBorderSide.resolveWith(WidgetPropertyResolver<BorderSide?> callback) =
      _WidgetStateBorderSide;

  /// Creates a [WidgetStateBorderSide] from a [WidgetStateMap].
  ///
  /// {@macro flutter.widgets.WidgetStateProperty.fromMap}
  /// It should only be used with widgets that document support for
  /// [WidgetStateBorderSide] objects (throws an error if used as a
  /// regular [BorderSide].)
  ///
  /// Example:
  ///
  /// ```dart
  /// const Chip(
  ///   label: Text('Transceiver'),
  ///   side: WidgetStateBorderSide.fromMap(<WidgetStatesConstraint, BorderSide?>{
  ///     WidgetState.selected: BorderSide(color: Colors.red),
  ///     // returns null if not selected, deferring to default theme/widget value.
  ///   }),
  /// ),
  /// ```
  ///
  /// {@macro flutter.widgets.WidgetState.any}
  const factory WidgetStateBorderSide.fromMap(WidgetStateMap<BorderSide?> map) =
      _WidgetBorderSideMapper;

  /// Returns a [BorderSide] that's to be used when a Widget is in the
  /// specified state. Return null to defer to the default value of the
  /// widget or theme.
  @override
  BorderSide? resolve(Set<WidgetState> states);

  /// Linearly interpolate between two [WidgetStateProperty]s of [BorderSide].
  static WidgetStateProperty<BorderSide?>? lerp(
    WidgetStateProperty<BorderSide?>? a,
    WidgetStateProperty<BorderSide?>? b,
    double t,
  ) {
    // Avoid creating a _LerpSides object for a common case.
    if (a == null && b == null) {
      return null;
    }
    if (identical(a, b)) {
      return a;
    }
    return _LerpSides(a, b, t);
  }
}

class _LerpSides implements WidgetStateProperty<BorderSide?> {
  const _LerpSides(this.a, this.b, this.t);

  final WidgetStateProperty<BorderSide?>? a;
  final WidgetStateProperty<BorderSide?>? b;
  final double t;

  @override
  BorderSide? resolve(Set<WidgetState> states) {
    final BorderSide? resolvedA = a?.resolve(states);
    final BorderSide? resolvedB = b?.resolve(states);
    if (resolvedA == null && resolvedB == null) {
      return null;
    }
    if (resolvedA == null) {
      return BorderSide.lerp(
        BorderSide(width: 0, color: resolvedB!.color.withAlpha(0)),
        resolvedB,
        t,
      );
    }
    if (resolvedB == null) {
      return BorderSide.lerp(
        resolvedA,
        BorderSide(width: 0, color: resolvedA.color.withAlpha(0)),
        t,
      );
    }
    return BorderSide.lerp(resolvedA, resolvedB, t);
  }
}

class _WidgetStateBorderSide extends WidgetStateBorderSide {
  const _WidgetStateBorderSide(this._resolve);

  final WidgetPropertyResolver<BorderSide?> _resolve;

  @override
  BorderSide? resolve(Set<WidgetState> states) => _resolve(states);
}

class _WidgetBorderSideMapper extends WidgetStateMapper<BorderSide?>
    implements WidgetStateBorderSide {
  const _WidgetBorderSideMapper(super.map);
}

/// Defines an [OutlinedBorder] whose value depends on a set of [WidgetState]s
/// which represent the interactive state of a component.
///
/// A [WidgetStateOutlinedBorder] can be created in one of the following ways:
///   1. Create a subclass of [WidgetStateOutlinedBorder] and implement the abstract `resolve` method.
///   2. Use [WidgetStateOutlinedBorder.resolveWith] and pass in a callback that
///      will be used to resolve the color in the given states.
///   3. Use [WidgetStateOutlinedBorder.fromMap] to assign a value using a [WidgetStateMap].
///
/// {@tool dartpad}
/// This example defines a subclass of [RoundedRectangleBorder] and an
/// implementation of [WidgetStateOutlinedBorder], that resolves to
/// [RoundedRectangleBorder] when its widget is selected.
///
/// ** See code in examples/api/lib/widgets/widget_state/widget_state_outlined_border.0.dart **
/// {@end-tool}
///
/// This class should only be used for parameters which are documented to take
/// [WidgetStateOutlinedBorder], otherwise only the default state will be used.
///
/// See also:
///
///  * [ShapeBorder] the base class for shape outlines.
abstract class WidgetStateOutlinedBorder extends OutlinedBorder
    implements WidgetStateProperty<OutlinedBorder?> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const WidgetStateOutlinedBorder();

  /// Creates a [WidgetStateOutlinedBorder] using a [WidgetPropertyResolver]
  /// callback.
  ///
  /// This constructor should only be used with widgets that support
  /// [WidgetStateOutlinedBorder], such as [ChipThemeData.shape]
  /// (if used as a regular [OutlinedBorder], it acts the same as
  /// an empty `RoundedRectangleBorder()` constructor).
  const factory WidgetStateOutlinedBorder.resolveWith(
    WidgetPropertyResolver<OutlinedBorder?> callback,
  ) = _WidgetStateOutlinedBorder;

  /// Creates a [WidgetStateOutlinedBorder] from a [WidgetStateMap].
  ///
  /// {@macro flutter.widgets.WidgetStateProperty.fromMap}
  /// It should only be used with widgets that support
  /// [WidgetStateOutlinedBorder], such as [ChipThemeData.shape]
  /// (throws an error if used as a regular [OutlinedBorder]).
  ///
  /// Resolves to `null` if no keys match, deferring to the default value
  /// of the widget or theme.
  const factory WidgetStateOutlinedBorder.fromMap(WidgetStateMap<OutlinedBorder?> map) =
      _WidgetOutlinedBorderMapper;

  /// Returns an [OutlinedBorder] that's to be used when a component is in the
  /// specified state. Return null to defer to the default value of the widget
  /// or theme.
  @override
  OutlinedBorder? resolve(Set<WidgetState> states);
}

class _WidgetStateOutlinedBorder extends RoundedRectangleBorder
    implements WidgetStateOutlinedBorder {
  const _WidgetStateOutlinedBorder(this._resolve);

  final WidgetPropertyResolver<OutlinedBorder?> _resolve;

  @override
  OutlinedBorder? resolve(Set<WidgetState> states) => _resolve(states);
}

class _WidgetOutlinedBorderMapper extends WidgetStateMapper<OutlinedBorder?>
    implements WidgetStateOutlinedBorder {
  const _WidgetOutlinedBorderMapper(super.map);
}

/// Defines a [TextStyle] that is also a [WidgetStateProperty].
///
/// This class exists to enable widgets with [TextStyle] valued properties
/// to also accept [WidgetStateProperty<TextStyle>] values. A widget
/// state text style property represents a text style which depends on
/// a widget's "interactive state". This state is represented as a
/// [Set] of [WidgetState]s, like [WidgetState.pressed],
/// [WidgetState.focused] and [WidgetState.hovered].
///
/// [WidgetStateTextStyle] should only be used with widgets that document
/// their support, like [InputDecoration.labelStyle].
///
/// A [WidgetStateTextStyle] can be created in one of the following ways:
///   1. Create a subclass of [WidgetStateTextStyle] and implement the abstract `resolve` method.
///   2. Use [WidgetStateTextStyle.resolveWith] and pass in a callback that
///      will be used to resolve the text style in the given states.
///   3. Use [WidgetStateTextStyle.fromMap] to assign a style using a [WidgetStateMap].
abstract class WidgetStateTextStyle extends TextStyle implements WidgetStateProperty<TextStyle> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const WidgetStateTextStyle();

  /// Creates a [WidgetStateTextStyle] from a [WidgetPropertyResolver<TextStyle>]
  /// callback function.
  ///
  /// Behaves like an empty `TextStyle()` constructor if used as a
  /// regular [TextStyle].
  ///
  /// The given callback parameter must return a non-null text style in the default
  /// state.
  const factory WidgetStateTextStyle.resolveWith(WidgetPropertyResolver<TextStyle> callback) =
      _WidgetStateTextStyle;

  /// Creates a [WidgetStateTextStyle] from a [WidgetStateMap].
  ///
  /// {@macro flutter.widgets.WidgetStateProperty.fromMap}
  /// It should only be used with widgets that document support for
  /// [WidgetStateTextStyle] objects (throws an error if used as a regular
  /// [TextStyle]).
  ///
  /// {@macro flutter.widgets.WidgetState.any}
  const factory WidgetStateTextStyle.fromMap(WidgetStateMap<TextStyle> map) =
      _WidgetTextStyleMapper;

  /// Returns a [TextStyle] that's to be used when a component is in the
  /// specified state.
  @override
  TextStyle resolve(Set<WidgetState> states);
}

class _WidgetStateTextStyle extends WidgetStateTextStyle {
  const _WidgetStateTextStyle(this._resolve);

  final WidgetPropertyResolver<TextStyle> _resolve;

  @override
  TextStyle resolve(Set<WidgetState> states) => _resolve(states);
}

class _WidgetTextStyleMapper extends WidgetStateMapper<TextStyle> implements WidgetStateTextStyle {
  const _WidgetTextStyleMapper(super.map);
}

/// Interface for classes that [resolve] to a value of type `T` based
/// on a widget's interactive "state", which is defined as a set
/// of [WidgetState]s.
///
/// Widget state properties represent values that depend on a widget's "state".
/// The state is encoded as a set of [WidgetState] values, like
/// [WidgetState.focused], [WidgetState.hovered], [WidgetState.pressed]. For
/// example the [InkWell.overlayColor] defines the color that fills the ink well
/// when it's pressed (the "splash color"), focused, or hovered. The [InkWell]
/// uses the overlay color's [resolve] method to compute the color for the
/// ink well's current state.
///
/// [ButtonStyle], which is used to configure the appearance of
/// buttons like [TextButton], [ElevatedButton], and [OutlinedButton],
/// has many material state properties. The button widgets keep track
/// of their current material state and [resolve] the button style's
/// material state properties when their value is needed.
///
/// {@tool dartpad}
/// This example shows how the default text and icon color
/// (the "foreground color") of a [TextButton] can be overridden with a
/// [WidgetStateProperty]. In this example, the button's text color will be
/// colored differently depending on whether the button is pressed, hovered,
/// or focused.
///
/// ** See code in examples/api/lib/widgets/widget_state/widget_state_property.0.dart **
/// {@end-tool}
///
/// ## Performance Consideration
///
/// In order for constructed [WidgetStateProperty] objects to be recognized as
/// equivalent, they need to either be `const` objects, or have overrides for
/// [operator==] and [hashCode].
///
/// This comes into play when, for instance, two [ThemeData] objects are being
/// compared for equality.
///
/// For a concrete `WidgetStateProperty` object that supports stable
/// equality checks, consider using [WidgetStateMapper].
///
/// See also:
///
///  * [MaterialStateProperty], the Material specific version of
///    `WidgetStateProperty`.
/// {@macro flutter.widgets.WidgetStateProperty.implementations}
abstract class WidgetStateProperty<T> {
  /// This abstract constructor allows extending the class.
  ///
  /// [WidgetStateProperty] is designed as an interface, so this constructor
  /// is only needed for backward compatibility.
  WidgetStateProperty();

  /// Creates a property that resolves using a [WidgetStateMap].
  ///
  /// {@template flutter.widgets.WidgetStateProperty.fromMap}
  /// This constructor's [resolve] method finds the first [MapEntry] whose
  /// key is satisfied by the set of states, and returns its associated value.
  /// {@endtemplate}
  ///
  /// Resolves to `null` if no keys match, or if [T] is non-nullable,
  /// the method throws an [ArgumentError].
  /// {@macro flutter.widgets.WidgetState.any}
  ///
  /// {@macro flutter.widgets.WidgetStateMap}
  const factory WidgetStateProperty.fromMap(WidgetStateMap<T> map) = WidgetStateMapper<T>;

  /// Resolves the value for the given set of states if `value` is a
  /// [WidgetStateProperty], otherwise returns the value itself.
  ///
  /// This is useful for widgets that have parameters which can optionally be a
  /// [WidgetStateProperty]. For example, [InkWell.mouseCursor] can be a
  /// [MouseCursor] or a [WidgetStateProperty<MouseCursor>].
  static T resolveAs<T>(T value, Set<WidgetState> states) {
    if (value is WidgetStateProperty<T>) {
      final WidgetStateProperty<T> property = value;
      return property.resolve(states);
    }
    return value;
  }

  /// Convenience method for creating a [WidgetStateProperty] from a
  /// [WidgetPropertyResolver] function alone.
  static WidgetStateProperty<T> resolveWith<T>(WidgetPropertyResolver<T> callback) =>
      _WidgetStatePropertyWith<T>(callback);

  /// Convenience method for creating a [WidgetStateProperty] that resolves
  /// to a single value for all states.
  ///
  /// Prefer using [WidgetStatePropertyAll] directly, which allows for creating
  /// `const` values.
  ///
  // TODO(darrenaustin): Deprecate this when we have the ability to create
  // a dart fix that will replace this with WidgetStatePropertyAll:
  // https://github.com/dart-lang/sdk/issues/49056.
  static WidgetStateProperty<T> all<T>(T value) => WidgetStatePropertyAll<T>(value);

  /// Linearly interpolate between two [WidgetStateProperty]s.
  static WidgetStateProperty<T?>? lerp<T>(
    WidgetStateProperty<T>? a,
    WidgetStateProperty<T>? b,
    double t,
    T? Function(T?, T?, double) lerpFunction,
  ) {
    // Avoid creating a _LerpProperties object for a common case.
    if (a == null && b == null) {
      return null;
    }
    return _LerpProperties<T>(a, b, t, lerpFunction);
  }

  /// Returns a value of type `T` that depends on [states].
  ///
  /// Widgets like [TextButton] and [ElevatedButton] apply this method to their
  /// current [WidgetState]s to compute colors and other visual parameters
  /// at build time.
  T resolve(Set<WidgetState> states);
}

class _LerpProperties<T> implements WidgetStateProperty<T?> {
  const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);

  final WidgetStateProperty<T>? a;
  final WidgetStateProperty<T>? b;
  final double t;
  final T? Function(T?, T?, double) lerpFunction;

  @override
  T? resolve(Set<WidgetState> states) {
    final T? resolvedA = a?.resolve(states);
    final T? resolvedB = b?.resolve(states);
    return lerpFunction(resolvedA, resolvedB, t);
  }
}

class _WidgetStatePropertyWith<T> implements WidgetStateProperty<T> {
  _WidgetStatePropertyWith(this._resolve);

  final WidgetPropertyResolver<T> _resolve;

  @override
  T resolve(Set<WidgetState> states) => _resolve(states);
}

/// A [Map] used to resolve to a single value of type `T` based on
/// the current set of Widget states.
///
/// {@template flutter.widgets.WidgetStateMap}
/// Example:
///
/// ```dart
/// // This WidgetStateMap<Color?> resolves to null if no keys match.
/// WidgetStateProperty<Color?>.fromMap(<WidgetStatesConstraint, Color?>{
///   WidgetState.error: Colors.red,
///   WidgetState.hovered & WidgetState.focused: Colors.blueAccent,
///   WidgetState.focused: Colors.blue,
///   ~WidgetState.disabled: Colors.black,
/// });
///
/// // The same can be accomplished with a WidgetPropertyResolver,
/// // but it's more verbose:
/// WidgetStateProperty.resolveWith<Color?>((Set<WidgetState> states) {
///   if (states.contains(WidgetState.error)) {
///     return Colors.red;
///   } else if (states.contains(WidgetState.hovered) && states.contains(WidgetState.focused)) {
///     return Colors.blueAccent;
///   } else if (states.contains(WidgetState.focused)) {
///     return Colors.blue;
///   } else if (!states.contains(WidgetState.disabled)) {
///     return Colors.black;
///   }
///   return null;
/// });
/// ```
///
/// A widget state combination can be stored in a variable,
/// and [WidgetState.any] can be used for non-nullable types to ensure
/// that there's a match:
///
/// ```dart
/// final WidgetStatesConstraint selectedError = WidgetState.selected & WidgetState.error;
///
/// final WidgetStateProperty<Color> color = WidgetStateProperty<Color>.fromMap(
///   <WidgetStatesConstraint, Color>{
///     selectedError & WidgetState.hovered: Colors.redAccent,
///     selectedError: Colors.red,
///     WidgetState.any: Colors.black,
///   },
/// );
///
/// // The (more verbose) WidgetPropertyResolver implementation:
/// final WidgetStateProperty<Color> colorResolveWith = WidgetStateProperty.resolveWith<Color>(
///   (Set<WidgetState> states) {
///     if (states.containsAll(<WidgetState>{WidgetState.selected, WidgetState.error})) {
///       if (states.contains(WidgetState.hovered)) {
///         return Colors.redAccent;
///       }
///       return Colors.red;
///     }
///     return Colors.black;
///   },
/// );
/// ```
/// {@endtemplate}
typedef WidgetStateMap<T> = Map<WidgetStatesConstraint, T>;

/// Uses a [WidgetStateMap] to resolve to a single value of type `T` based on
/// the current set of Widget states.
///
/// {@macro flutter.widgets.WidgetStateMap}
///
/// Classes that extend [WidgetStateMapper] can implement any other interface,
/// but should only be used for fields that document their support for
/// [WidgetStateProperty] objects.
///
/// The only exceptions are classes such as [double] that are marked as
/// `base` or `final`, since they can't be implemented—a [double] property
/// can't be set up to also accept [WidgetStateProperty] objects
/// and would need to pick one or the other.
///
/// For example, a [WidgetStateColor.fromMap] object can be passed anywhere that
/// accepts either a [Color] or a [WidgetStateProperty] object, but attempting
/// to access a [Color] field (such as [Color.value]) on the mapper object
/// throws a [FlutterError].
@immutable
class WidgetStateMapper<T> with Diagnosticable implements WidgetStateProperty<T> {
  /// Creates a [WidgetStateProperty] object that can resolve
  /// to a value of type [T] using the provided [map].
  const WidgetStateMapper(WidgetStateMap<T> map) : _map = map;

  final WidgetStateMap<T> _map;

  @override
  T resolve(Set<WidgetState> states) {
    for (final MapEntry<WidgetStatesConstraint, T> entry in _map.entries) {
      if (entry.key.isSatisfiedBy(states)) {
        return entry.value;
      }
    }

    try {
      return null as T;
    } on TypeError {
      throw ArgumentError(
        'The current set of widget states is $states.\n'
        'None of the provided map keys matched this set, '
        'and the type "$T" is non-nullable.\n'
        'Consider using "WidgetStateMapper<$T?>()", '
        'or adding the "WidgetState.any" key to this map.',
      );
    }
  }

  @override
  bool operator ==(Object other) {
    return other is WidgetStateMapper<T> && mapEquals(_map, other._map);
  }

  @override
  int get hashCode => MapEquality<WidgetStatesConstraint, T>().hash(_map);

  @override
  String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
    return 'WidgetStateMapper<$T>($_map)';
  }

  @override
  Never noSuchMethod(Invocation invocation) {
    throw FlutterError.fromParts(<DiagnosticsNode>[
      ErrorSummary(
        'There was an attempt to access the "${invocation.memberName}" '
        'field of a WidgetStateMapper<$T> object.',
      ),
      ErrorDescription('$this'),
      ErrorDescription(
        'WidgetStateProperty objects should only be used '
        'in places that document their support.',
      ),
      ErrorHint(
        'Double-check whether the map was used in a place that '
        'documents support for WidgetStateProperty objects. If so, '
        'please file a bug report. (The https://pub.dev/ page for a package '
        'contains a link to "View/report issues".)',
      ),
    ]);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties, {String prefix = ''}) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<WidgetStateMap<T>>('map', _map));
  }
}

/// Convenience class for creating a [WidgetStateProperty] that
/// resolves to the given value for all states.
///
/// See also:
///
///  * [MaterialStatePropertyAll], the Material specific version of
///    `WidgetStatePropertyAll`.
@immutable
class WidgetStatePropertyAll<T> implements WidgetStateProperty<T> {
  /// Constructs a [WidgetStateProperty] that always resolves to the given
  /// value.
  const WidgetStatePropertyAll(this.value);

  /// The value of the property that will be used for all states.
  final T value;

  @override
  T resolve(Set<WidgetState> states) => value;

  @override
  String toString() {
    if (value is double) {
      return 'WidgetStatePropertyAll(${debugFormatDouble(value as double)})';
    } else {
      return 'WidgetStatePropertyAll($value)';
    }
  }

  @override
  bool operator ==(Object other) {
    return other is WidgetStatePropertyAll<T> &&
        other.runtimeType == runtimeType &&
        other.value == value;
  }

  @override
  int get hashCode => value.hashCode;
}

/// Manages a set of [WidgetState]s and notifies listeners of changes.
///
/// Used by widgets that expose their internal state for the sake of
/// extensions that add support for additional states. See
/// [TextButton] for an example.
///
/// The controller's [value] is its current set of states. Listeners
/// are notified whenever the [value] changes. The [value] should only be
/// changed with [update]; it should not be modified directly.
///
/// The controller's [value] represents the set of states that a
/// widget's visual properties, typically [WidgetStateProperty]
/// values, are resolved against. It is _not_ the intrinsic state of
/// the widget. The widget is responsible for ensuring that the
/// controller's [value] tracks its intrinsic state. For example one
/// cannot request the keyboard focus for a widget by adding
/// [WidgetState.focused] to its controller. When the widget gains the
/// or loses the focus it will [update] its controller's [value] and
/// notify listeners of the change.
///
/// When calling `setState` in a [WidgetStatesController] listener, use the
/// [SchedulerBinding.addPostFrameCallback] to delay the call to `setState` after
/// the frame has been rendered. It's generally prudent to use the
/// [SchedulerBinding.addPostFrameCallback] because some of the widgets that
/// depend on [WidgetStatesController] may call [update] in their build method.
/// In such cases, listener's that call `setState` - during the build phase - will cause
/// an error.
///
/// See also:
///
///  * [MaterialStatesController], the Material specific version of
///    `WidgetStatesController`.
class WidgetStatesController extends ValueNotifier<Set<WidgetState>> {
  /// Creates a WidgetStatesController.
  WidgetStatesController([Set<WidgetState>? value]) : super(<WidgetState>{...?value});

  /// Adds [state] to [value] if [add] is true, and removes it otherwise,
  /// and notifies listeners if [value] has changed.
  void update(WidgetState state, bool add) {
    final bool valueChanged = add ? value.add(state) : value.remove(state);
    if (valueChanged) {
      notifyListeners();
    }
  }
}
