// 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/services.dart';
library;

import 'dart:async';
import 'dart:ui';

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

import 'basic.dart';
import 'binding.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
import 'media_query.dart';
import 'navigator.dart';
import 'pop_scope.dart';
import 'restoration.dart';
import 'restoration_properties.dart';
import 'routes.dart';
import 'view.dart';
import 'will_pop_scope.dart';

// Duration for delay before announcement in IOS so that the announcement won't be interrupted.
const Duration _kIOSAnnouncementDelayDuration = Duration(seconds: 1);

// Examples can assume:
// late BuildContext context;

/// An optional container for grouping together multiple form field widgets
/// (e.g. [TextField] widgets).
///
/// Each individual form field should be wrapped in a [FormField] widget, with
/// the [Form] widget as a common ancestor of all of those. Call methods on
/// [FormState] to save, reset, or validate each [FormField] that is a
/// descendant of this [Form]. To obtain the [FormState], you may use [Form.of]
/// with a context whose ancestor is the [Form], or pass a [GlobalKey] to the
/// [Form] constructor and call [GlobalKey.currentState].
///
/// {@tool dartpad}
/// This example shows a [Form] with one [TextFormField] to enter an email
/// address and an [ElevatedButton] to submit the form. A [GlobalKey] is used here
/// to identify the [Form] and validate input.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/form.png)
///
/// ** See code in examples/api/lib/widgets/form/form.0.dart **
/// {@end-tool}
///
/// See also:
///
///  * [GlobalKey], a key that is unique across the entire app.
///  * [FormField], a single form field widget that maintains the current state.
///  * [TextFormField], a convenience widget that wraps a [TextField] widget in a [FormField].
class Form extends StatefulWidget {
  /// Creates a container for form fields.
  const Form({
    super.key,
    required this.child,
    this.canPop,
    @Deprecated(
      'Use onPopInvokedWithResult instead. '
      'This feature was deprecated after v3.22.0-12.0.pre.',
    )
    this.onPopInvoked,
    this.onPopInvokedWithResult,
    @Deprecated(
      'Use canPop and/or onPopInvokedWithResult instead. '
      'This feature was deprecated after v3.12.0-1.0.pre.',
    )
    this.onWillPop,
    this.onChanged,
    AutovalidateMode? autovalidateMode,
  }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled,
       assert(
         onPopInvokedWithResult == null || onPopInvoked == null,
         'onPopInvoked is deprecated; use onPopInvokedWithResult',
       ),
       assert(
         ((onPopInvokedWithResult ?? onPopInvoked ?? canPop) == null) || onWillPop == null,
         'onWillPop is deprecated; use canPop and/or onPopInvokedWithResult.',
       );

  /// Returns the [FormState] of the closest [Form] widget which encloses the
  /// given context, or null if none is found.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// FormState? form = Form.maybeOf(context);
  /// form?.save();
  /// ```
  ///
  /// Calling this method will create a dependency on the closest [Form] in the
  /// [context], if there is one.
  ///
  /// See also:
  ///
  /// * [Form.of], which is similar to this method, but asserts if no [Form]
  ///   ancestor is found.
  static FormState? maybeOf(BuildContext context) {
    final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
    return scope?._formState;
  }

  /// Returns the [FormState] of the closest [Form] widget which encloses the
  /// given context.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// FormState form = Form.of(context);
  /// form.save();
  /// ```
  ///
  /// If no [Form] ancestor is found, this will assert in debug mode, and throw
  /// an exception in release mode.
  ///
  /// Calling this method will create a dependency on the closest [Form] in the
  /// [context].
  ///
  /// See also:
  ///
  /// * [Form.maybeOf], which is similar to this method, but returns null if no
  ///   [Form] ancestor is found.
  static FormState of(BuildContext context) {
    final FormState? formState = maybeOf(context);
    assert(() {
      if (formState == null) {
        throw FlutterError(
          'Form.of() was called with a context that does not contain a Form widget.\n'
          'No Form widget ancestor could be found starting from the context that '
          'was passed to Form.of(). This can happen because you are using a widget '
          'that looks for a Form ancestor, but no such ancestor exists.\n'
          'The context used was:\n'
          '  $context',
        );
      }
      return true;
    }());
    return formState!;
  }

  /// The widget below this widget in the tree.
  ///
  /// This is the root of the widget hierarchy that contains this form.
  ///
  /// {@macro flutter.widgets.ProxyWidget.child}
  final Widget child;

  /// Enables the form to veto attempts by the user to dismiss the [ModalRoute]
  /// that contains the form.
  ///
  /// If the callback returns a Future that resolves to false, the form's route
  /// will not be popped.
  ///
  /// See also:
  ///
  ///  * [WillPopScope], another widget that provides a way to intercept the
  ///    back button.
  @Deprecated(
    'Use canPop and/or onPopInvokedWithResult instead. '
    'This feature was deprecated after v3.12.0-1.0.pre.',
  )
  final WillPopCallback? onWillPop;

  /// {@macro flutter.widgets.PopScope.canPop}
  ///
  /// {@tool dartpad}
  /// This sample demonstrates how to use this parameter to show a confirmation
  /// dialog when a navigation pop would cause form data to be lost.
  ///
  /// ** See code in examples/api/lib/widgets/form/form.1.dart **
  /// {@end-tool}
  ///
  /// See also:
  ///
  ///  * [onPopInvokedWithResult], which also comes from [PopScope] and is often used in
  ///    conjunction with this parameter.
  ///  * [PopScope.canPop], which is what [Form] delegates to internally.
  final bool? canPop;

  /// {@macro flutter.widgets.navigator.onPopInvokedWithResult}
  @Deprecated(
    'Use onPopInvokedWithResult instead. '
    'This feature was deprecated after v3.22.0-12.0.pre.',
  )
  final PopInvokedCallback? onPopInvoked;

  /// {@macro flutter.widgets.navigator.onPopInvokedWithResult}
  ///
  /// {@tool dartpad}
  /// This sample demonstrates how to use this parameter to show a confirmation
  /// dialog when a navigation pop would cause form data to be lost.
  ///
  /// ** See code in examples/api/lib/widgets/form/form.1.dart **
  /// {@end-tool}
  ///
  /// See also:
  ///
  ///  * [canPop], which also comes from [PopScope] and is often used in
  ///    conjunction with this parameter.
  ///  * [PopScope.onPopInvokedWithResult], which is what [Form] delegates to internally.
  final PopInvokedWithResultCallback<Object?>? onPopInvokedWithResult;

  /// Called when one of the form fields changes.
  ///
  /// In addition to this callback being invoked, all the form fields themselves
  /// will rebuild.
  final VoidCallback? onChanged;

  /// Used to enable/disable form fields auto validation and update their error
  /// text.
  ///
  /// {@macro flutter.widgets.FormField.autovalidateMode}
  final AutovalidateMode autovalidateMode;

  void _callPopInvoked(bool didPop, Object? result) {
    if (onPopInvokedWithResult != null) {
      onPopInvokedWithResult!(didPop, result);
      return;
    }
    onPopInvoked?.call(didPop);
  }

  @override
  FormState createState() => FormState();
}

/// State associated with a [Form] widget.
///
/// A [FormState] object can be used to [save], [reset], and [validate] every
/// [FormField] that is a descendant of the associated [Form].
///
/// Typically obtained via [Form.of].
class FormState extends State<Form> {
  int _generation = 0;
  bool _hasInteractedByUser = false;
  final Set<FormFieldState<dynamic>> _fields = <FormFieldState<dynamic>>{};

  // Called when a form field has changed. This will cause all form fields
  // to rebuild, useful if form fields have interdependencies.
  void _fieldDidChange() {
    widget.onChanged?.call();

    _hasInteractedByUser = _fields.any(
      (FormFieldState<dynamic> field) => field._hasInteractedByUser.value,
    );

    _forceRebuild();
  }

  void _forceRebuild() {
    setState(() {
      ++_generation;
    });
  }

  void _register(FormFieldState<dynamic> field) {
    _fields.add(field);
  }

  void _unregister(FormFieldState<dynamic> field) {
    _fields.remove(field);
  }

  @protected
  @override
  Widget build(BuildContext context) {
    final bool hasError = _fields.any((FormFieldState<dynamic> field) => field.hasError);

    switch (widget.autovalidateMode) {
      case AutovalidateMode.always:
        _validate(View.of(context));
      case AutovalidateMode.onUserInteraction:
        if (_hasInteractedByUser) {
          _validate(View.of(context));
        }
      case AutovalidateMode.onUserInteractionIfError:
        if (_hasInteractedByUser && hasError) {
          _validate(View.of(context));
        }
      case AutovalidateMode.onUnfocus:
      case AutovalidateMode.disabled:
        break;
    }

    final Widget form;
    if (widget.canPop != null || (widget.onPopInvokedWithResult ?? widget.onPopInvoked) != null) {
      form = PopScope<Object?>(
        canPop: widget.canPop ?? true,
        onPopInvokedWithResult: widget._callPopInvoked,
        child: _FormScope(formState: this, generation: _generation, child: widget.child),
      );
    } else {
      form = WillPopScope(
        onWillPop: widget.onWillPop,
        child: _FormScope(formState: this, generation: _generation, child: widget.child),
      );
    }
    return Semantics(
      container: true,
      explicitChildNodes: true,
      role: SemanticsRole.form,
      child: form,
    );
  }

  /// Saves every [FormField] that is a descendant of this [Form].
  void save() {
    for (final FormFieldState<dynamic> field in _fields) {
      field.save();
    }
  }

  /// Resets every [FormField] that is a descendant of this [Form] back to its
  /// [FormField.initialValue].
  ///
  /// The [Form.onChanged] callback will be called.
  ///
  /// If the form's [Form.autovalidateMode] property is [AutovalidateMode.always],
  /// the fields will all be revalidated after being reset.
  void reset() {
    for (final FormFieldState<dynamic> field in _fields) {
      field.reset();
    }
    _hasInteractedByUser = false;
    _fieldDidChange();
  }

  /// Validates every [FormField] that is a descendant of this [Form], and
  /// returns true if there are no errors.
  ///
  /// The form will rebuild to report the results.
  ///
  /// See also:
  ///  * [validateGranularly], which also validates descendant [FormField]s,
  /// but instead returns a [Set] of fields with errors.
  bool validate() {
    _hasInteractedByUser = true;
    _forceRebuild();
    return _validate(View.of(context));
  }

  /// Validates every [FormField] that is a descendant of this [Form], and
  /// returns a [Set] of [FormFieldState] of the invalid field(s) only, if any.
  ///
  /// This method can be useful to highlight field(s) with errors.
  ///
  /// The form will rebuild to report the results.
  ///
  /// See also:
  ///  * [validate], which also validates descendant [FormField]s,
  /// and return true if there are no errors.
  Set<FormFieldState<Object?>> validateGranularly() {
    final invalidFields = <FormFieldState<Object?>>{};
    _hasInteractedByUser = true;
    _forceRebuild();
    _validate(View.of(context), invalidFields);
    return invalidFields;
  }

  bool _validate(FlutterView view, [Set<FormFieldState<Object?>>? invalidFields]) {
    var hasError = false;
    var errorMessage = '';
    final validateOnFocusChange = widget.autovalidateMode == AutovalidateMode.onUnfocus;

    for (final FormFieldState<dynamic> field in _fields) {
      final bool hasFocus = field._focusNode.hasFocus;

      if (!validateOnFocusChange || !hasFocus || (validateOnFocusChange && hasFocus)) {
        final bool isFieldValid = field.validate();
        hasError |= !isFieldValid;
        // Ensure that only the first error message gets announced to the user.
        if (errorMessage.isEmpty) {
          errorMessage = field.errorText ?? '';
        }
        if (invalidFields != null && !isFieldValid) {
          invalidFields.add(field);
        }
      }
    }

    if (errorMessage.isNotEmpty && MediaQuery.supportsAnnounceOf(context)) {
      final TextDirection directionality = Directionality.of(context);
      if (defaultTargetPlatform == TargetPlatform.iOS) {
        unawaited(
          Future<void>(() async {
            await Future<void>.delayed(_kIOSAnnouncementDelayDuration);
            SemanticsService.sendAnnouncement(
              view,
              errorMessage,
              directionality,
              assertiveness: Assertiveness.assertive,
            );
          }),
        );
      } else {
        SemanticsService.sendAnnouncement(
          view,
          errorMessage,
          directionality,
          assertiveness: Assertiveness.assertive,
        );
      }
    }

    return !hasError;
  }
}

class _FormScope extends InheritedWidget {
  const _FormScope({required super.child, required FormState formState, required int generation})
    : _formState = formState,
      _generation = generation;

  final FormState _formState;

  /// Incremented every time a form field has changed. This lets us know when
  /// to rebuild the form.
  final int _generation;

  /// The [Form] associated with this widget.
  Form get form => _formState.widget;

  @override
  bool updateShouldNotify(_FormScope old) => _generation != old._generation;
}

/// Signature for validating a form field.
///
/// Returns an error string to display if the input is invalid, or null
/// otherwise.
///
/// Used by [FormField.validator].
typedef FormFieldValidator<T> = String? Function(T? value);

/// Signature for a callback that builds an error widget.
///
/// See also:
///
///  * [FormField.errorBuilder], which is of this type, and passes the result error
/// given by [TextFormField.validator].
typedef FormFieldErrorBuilder = Widget Function(BuildContext context, String errorText);

/// Signature for being notified when a form field changes value.
///
/// Used by [FormField.onSaved].
typedef FormFieldSetter<T> = void Function(T? newValue);

/// Signature for building the widget representing the form field.
///
/// Used by [FormField.builder].
typedef FormFieldBuilder<T> = Widget Function(FormFieldState<T> field);

/// A single form field.
///
/// This widget maintains the current state of the form field, so that updates
/// and validation errors are visually reflected in the UI.
///
/// When used inside a [Form], you can use methods on [FormState] to query or
/// manipulate the form data as a whole. For example, calling [FormState.save]
/// will invoke each [FormField]'s [onSaved] callback in turn.
///
/// Use a [GlobalKey] with [FormField] if you want to retrieve its current
/// state, for example if you want one form field to depend on another.
///
/// A [Form] ancestor is not required. The [Form] allows one to
/// save, reset, or validate multiple fields at once. To use without a [Form],
/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
/// save or reset the form field.
///
/// See also:
///
///  * [Form], which is the widget that aggregates the form fields.
///  * [TextField], which is a commonly used form field for entering text.
class FormField<T> extends StatefulWidget {
  /// Creates a single form field.
  const FormField({
    super.key,
    required this.builder,
    this.onSaved,
    this.onReset,
    this.forceErrorText,
    this.validator,
    this.errorBuilder,
    this.initialValue,
    this.enabled = true,
    AutovalidateMode? autovalidateMode,
    this.restorationId,
  }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;

  /// Function that returns the widget representing this form field.
  ///
  /// It is passed the form field state as input, containing the current value
  /// and validation state of this field.
  final FormFieldBuilder<T> builder;

  /// An optional method to call with the final value when the form is saved via
  /// [FormState.save].
  final FormFieldSetter<T>? onSaved;

  /// An optional method to call when the form field is reset via
  /// [FormFieldState.reset].
  final VoidCallback? onReset;

  /// An optional property that forces the [FormFieldState] into an error state
  /// by directly setting the [FormFieldState.errorText] property without
  /// running the validator function.
  ///
  /// When the [forceErrorText] property is provided, the [FormFieldState.errorText]
  /// will be set to the provided value, causing the form field to be considered
  /// invalid and to display the error message specified.
  ///
  /// When [validator] is provided, [forceErrorText] will override any error that it
  /// returns. [validator] will not be called unless [forceErrorText] is null.
  ///
  /// See also:
  ///
  /// * [InputDecoration.errorText], which is used to display error messages in the text
  /// field's decoration without effecting the field's state. When [forceErrorText] is
  /// not null, it will override [InputDecoration.errorText] value.
  final String? forceErrorText;

  /// An optional method that validates an input. Returns an error string to
  /// display if the input is invalid, or null otherwise.
  ///
  /// The returned value is exposed by the [FormFieldState.errorText] property.
  /// The [TextFormField] uses this to override the [InputDecoration.errorText]
  /// value.
  ///
  /// Alternating between error and normal state can cause the height of the
  /// [TextFormField] to change if no other subtext decoration is set on the
  /// field. To create a field whose height is fixed regardless of whether or
  /// not an error is displayed, either wrap the  [TextFormField] in a fixed
  /// height parent like [SizedBox], or set the [InputDecoration.helperText]
  /// parameter to a space.
  final FormFieldValidator<T>? validator;

  /// Function that returns the widget representing the error to display.
  ///
  /// It is passed the form field validator error string as input.
  /// The resulting widget is passed to [InputDecoration.error].
  ///
  /// If null, the validator error string is passed to
  /// [InputDecoration.errorText].
  final FormFieldErrorBuilder? errorBuilder;

  /// An optional value to initialize the form field to, or null otherwise.
  ///
  /// The `initialValue` affects the form field's state in two cases:
  /// 1. When the form field is first built, `initialValue` determines the field's initial state.
  /// 2. When [FormFieldState.reset] is called (either directly or by calling
  ///    [FormFieldState.reset]), the form field is reset to this `initialValue`.
  final T? initialValue;

  /// Whether the form is able to receive user input.
  ///
  /// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled],
  /// the field will be auto validated. Likewise, if this field is false, the widget
  /// will not be validated regardless of [autovalidateMode].
  final bool enabled;

  /// Used to enable/disable this form field auto validation and update its
  /// error text.
  ///
  /// {@template flutter.widgets.FormField.autovalidateMode}
  /// If [AutovalidateMode.onUserInteraction], this FormField will only
  /// auto-validate after its content changes. If [AutovalidateMode.always], it
  /// will auto-validate even without user interaction. If
  /// [AutovalidateMode.disabled], auto-validation will be disabled.
  ///
  /// Defaults to [AutovalidateMode.disabled].
  /// {@endtemplate}
  final AutovalidateMode autovalidateMode;

  /// Restoration ID to save and restore the state of the form field.
  ///
  /// Setting the restoration ID to a non-null value results in whether or not
  /// the form field validation persists.
  ///
  /// The state of this widget is persisted in a [RestorationBucket] claimed
  /// from the surrounding [RestorationScope] using the provided restoration ID.
  ///
  /// See also:
  ///
  ///  * [RestorationManager], which explains how state restoration works in
  ///    Flutter.
  final String? restorationId;

  @override
  FormFieldState<T> createState() => FormFieldState<T>();
}

/// The current state of a [FormField]. Passed to the [FormFieldBuilder] method
/// for use in constructing the form field's widget.
class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
  late T? _value = widget.initialValue;
  // Marking it as late, so it can be registered
  // with the value provided by [forceErrorText].
  late final RestorableStringN _errorText;
  final RestorableBool _hasInteractedByUser = RestorableBool(false);
  final FocusNode _focusNode = FocusNode();

  /// The current value of the form field.
  T? get value => _value;

  /// The current validation error returned by the [FormField.validator]
  /// callback, or the manually provided error message using the
  /// [FormField.forceErrorText] property.
  ///
  /// This property is automatically updated when [validate] is called and the
  /// [FormField.validator] callback is invoked, or If [FormField.forceErrorText] is set
  /// directly to a non-null value.
  String? get errorText => _errorText.value;

  /// True if this field has any validation errors.
  bool get hasError => _errorText.value != null;

  /// Returns true if the user has modified the value of this field.
  ///
  /// This only updates to true once [didChange] has been called and resets to
  /// false when [reset] is called.
  bool get hasInteractedByUser => _hasInteractedByUser.value;

  /// True if the current value is valid.
  ///
  /// This will not set [errorText] or [hasError] and it will not update
  /// error display.
  ///
  /// See also:
  ///
  ///  * [validate], which may update [errorText] and [hasError].
  ///
  ///  * [FormField.forceErrorText], which also may update [errorText] and [hasError].
  bool get isValid => widget.forceErrorText == null && widget.validator?.call(_value) == null;

  /// Calls the [FormField]'s onSaved method with the current value.
  void save() {
    widget.onSaved?.call(value);
  }

  /// Resets the field to its initial value.
  void reset() {
    setState(() {
      _value = widget.initialValue;
      _hasInteractedByUser.value = false;
      _errorText.value = null;
    });
    widget.onReset?.call();
    Form.maybeOf(context)?._fieldDidChange();
  }

  /// Calls [FormField.validator] to set the [errorText] only if [FormField.forceErrorText] is null.
  /// When [FormField.forceErrorText] is not null, [FormField.validator] will not be called.
  ///
  /// Returns true if there were no errors.
  /// See also:
  ///
  ///  * [isValid], which passively gets the validity without setting
  ///    [errorText] or [hasError].
  bool validate() {
    setState(() {
      _validate();
    });
    return !hasError;
  }

  void _validate() {
    if (widget.forceErrorText != null) {
      _errorText.value = widget.forceErrorText;
      // Skip validating if error is forced.
      return;
    }
    if (widget.validator != null) {
      _errorText.value = widget.validator!(_value);
    } else {
      _errorText.value = null;
    }
  }

  /// Updates this field's state to the new value. Useful for responding to
  /// child widget changes, e.g. [Slider]'s [Slider.onChanged] argument.
  ///
  /// Triggers the [Form.onChanged] callback and, if [Form.autovalidateMode] is
  /// [AutovalidateMode.always] or [AutovalidateMode.onUserInteraction],
  /// revalidates all the fields of the form.
  void didChange(T? value) {
    setState(() {
      _value = value;
      _hasInteractedByUser.value = true;
    });
    Form.maybeOf(context)?._fieldDidChange();
  }

  /// Sets the value associated with this form field.
  ///
  /// This method should only be called by subclasses that need to update
  /// the form field value due to state changes identified during the widget
  /// build phase, when calling `setState` is prohibited. In all other cases,
  /// the value should be set by a call to [didChange], which ensures that
  /// `setState` is called.
  @protected
  // ignore: use_setters_to_change_properties, (API predates enforcing the lint)
  void setValue(T? value) {
    _value = value;
  }

  @override
  String? get restorationId => widget.restorationId;

  @protected
  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_errorText, 'error_text');
    registerForRestoration(_hasInteractedByUser, 'has_interacted_by_user');
  }

  @protected
  @override
  void deactivate() {
    Form.maybeOf(context)?._unregister(this);
    super.deactivate();
  }

  @protected
  @override
  void initState() {
    super.initState();
    _errorText = RestorableStringN(widget.forceErrorText);
  }

  @protected
  @override
  void didUpdateWidget(FormField<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.forceErrorText != oldWidget.forceErrorText) {
      _errorText.value = widget.forceErrorText;
    }
  }

  @protected
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    switch (Form.maybeOf(context)?.widget.autovalidateMode) {
      case AutovalidateMode.always:
        WidgetsBinding.instance.addPostFrameCallback((_) {
          // If the form is already validated, don't validate again.
          if (widget.enabled && !hasError && !isValid) {
            validate();
          }
        });
      case AutovalidateMode.onUnfocus:
      case AutovalidateMode.onUserInteraction:
      case AutovalidateMode.onUserInteractionIfError:
      case AutovalidateMode.disabled:
      case null:
        break;
    }
  }

  @override
  void dispose() {
    _errorText.dispose();
    _focusNode.dispose();
    _hasInteractedByUser.dispose();
    super.dispose();
  }

  @protected
  @override
  Widget build(BuildContext context) {
    if (widget.enabled) {
      switch (widget.autovalidateMode) {
        case AutovalidateMode.always:
          _validate();
        case AutovalidateMode.onUserInteraction:
          if (_hasInteractedByUser.value) {
            _validate();
          }
        case AutovalidateMode.onUserInteractionIfError:
          if (_hasInteractedByUser.value && hasError) {
            _validate();
          }
        case AutovalidateMode.onUnfocus:
        case AutovalidateMode.disabled:
          break;
      }
    }

    Form.maybeOf(context)?._register(this);

    final Widget child = Semantics(
      validationResult: hasError
          ? SemanticsValidationResult.invalid
          : SemanticsValidationResult.valid,
      child: widget.builder(this),
    );

    if (Form.maybeOf(context)?.widget.autovalidateMode == AutovalidateMode.onUnfocus &&
            widget.autovalidateMode != AutovalidateMode.always ||
        widget.autovalidateMode == AutovalidateMode.onUnfocus) {
      return Focus(
        canRequestFocus: false,
        skipTraversal: true,
        onFocusChange: (bool value) {
          if (!value) {
            setState(() {
              _validate();
            });
          }
        },
        focusNode: _focusNode,
        child: child,
      );
    }

    return child;
  }
}

/// Used to configure the auto validation of [FormField] and [Form] widgets.
enum AutovalidateMode {
  /// No auto validation will occur.
  disabled,

  /// Used to auto-validate [Form] and [FormField] even without user interaction.
  always,

  /// Used to auto-validate [Form] and [FormField] only after each user
  /// interaction.
  onUserInteraction,

  /// Used to auto-validate [Form] and [FormField] only after the field has
  /// lost focus.
  ///
  /// In order to validate all fields of a [Form] after the first time the user interacts
  /// with one, use [always] instead.
  onUnfocus,

  /// Used to auto-validate [Form] and [FormField] after each user
  /// interaction, only if the the field already has an error.
  ///
  /// This is useful for reducing unnecessary validation calls while
  /// still ensuring errors are re-checked when the user attempts to fix them.
  onUserInteractionIfError,
}
