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

import 'dart:ui' show lerpDouble;

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

import 'theme.dart';

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

/// Defines a theme for [Dialog] widgets.
///
/// Descendant widgets obtain the current [DialogThemeData] object using
/// [DialogTheme.of]. Instances of [DialogThemeData] can be customized with
/// [DialogThemeData.copyWith].
///
/// [titleTextStyle] and [contentTextStyle] are used in [AlertDialog]s and [SimpleDialog]s.
///
/// See also:
///
///  * [Dialog], a dialog that can be customized using this [DialogTheme].
///  * [AlertDialog], a dialog that can be customized using this [DialogTheme].
///  * [SimpleDialog], a dialog that can be customized using this [DialogTheme].
///  * [ThemeData], which describes the overall theme information for the
///    application.
@immutable
class DialogTheme extends InheritedTheme with Diagnosticable {
  /// Creates a dialog theme that can be used for [ThemeData.dialogTheme].
  const DialogTheme({
    super.key,
    Color? backgroundColor,
    double? elevation,
    Color? shadowColor,
    Color? surfaceTintColor,
    ShapeBorder? shape,
    AlignmentGeometry? alignment,
    Color? iconColor,
    TextStyle? titleTextStyle,
    TextStyle? contentTextStyle,
    EdgeInsetsGeometry? actionsPadding,
    Color? barrierColor,
    EdgeInsets? insetPadding,
    Clip? clipBehavior,
    DialogThemeData? data,
    Widget? child,
  }) : assert(
         data == null ||
             (backgroundColor ??
                     elevation ??
                     shadowColor ??
                     surfaceTintColor ??
                     shape ??
                     alignment ??
                     iconColor ??
                     titleTextStyle ??
                     contentTextStyle ??
                     actionsPadding ??
                     barrierColor ??
                     insetPadding ??
                     clipBehavior) ==
                 null,
       ),
       _data = data,
       _backgroundColor = backgroundColor,
       _elevation = elevation,
       _shadowColor = shadowColor,
       _surfaceTintColor = surfaceTintColor,
       _shape = shape,
       _alignment = alignment,
       _iconColor = iconColor,
       _titleTextStyle = titleTextStyle,
       _contentTextStyle = contentTextStyle,
       _actionsPadding = actionsPadding,
       _barrierColor = barrierColor,
       _insetPadding = insetPadding,
       _clipBehavior = clipBehavior,
       super(child: child ?? const SizedBox());

  final DialogThemeData? _data;
  final Color? _backgroundColor;
  final double? _elevation;
  final Color? _shadowColor;
  final Color? _surfaceTintColor;
  final ShapeBorder? _shape;
  final AlignmentGeometry? _alignment;
  final TextStyle? _titleTextStyle;
  final TextStyle? _contentTextStyle;
  final EdgeInsetsGeometry? _actionsPadding;
  final Color? _iconColor;
  final Color? _barrierColor;
  final EdgeInsets? _insetPadding;
  final Clip? _clipBehavior;

  /// Overrides the default value for [Dialog.backgroundColor].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.backgroundColor] property in [data] instead.
  Color? get backgroundColor => _data != null ? _data.backgroundColor : _backgroundColor;

  /// Overrides the default value for [Dialog.elevation].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.elevation] property in [data] instead.
  double? get elevation => _data != null ? _data.elevation : _elevation;

  /// Overrides the default value for [Dialog.shadowColor].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.shadowColor] property in [data] instead.
  Color? get shadowColor => _data != null ? _data.shadowColor : _shadowColor;

  /// Overrides the default value for [Dialog.surfaceTintColor].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.surfaceTintColor] property in [data] instead.
  Color? get surfaceTintColor => _data != null ? _data.surfaceTintColor : _surfaceTintColor;

  /// Overrides the default value for [Dialog.shape].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.shape] property in [data] instead.
  ShapeBorder? get shape => _data != null ? _data.shape : _shape;

  /// Overrides the default value for [Dialog.alignment].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.alignment] property in [data] instead.
  AlignmentGeometry? get alignment => _data != null ? _data.alignment : _alignment;

  /// Overrides the default value for [DefaultTextStyle] for [SimpleDialog.title] and
  /// [AlertDialog.title].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.titleTextStyle] property in [data] instead.
  TextStyle? get titleTextStyle => _data != null ? _data.titleTextStyle : _titleTextStyle;

  /// Overrides the default value for [DefaultTextStyle] for [SimpleDialog.children] and
  /// [AlertDialog.content].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.contentTextStyle] property in [data] instead.
  TextStyle? get contentTextStyle => _data != null ? _data.contentTextStyle : _contentTextStyle;

  /// Overrides the default value for [AlertDialog.actionsPadding].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.actionsPadding] property in [data] instead.
  EdgeInsetsGeometry? get actionsPadding => _data != null ? _data.actionsPadding : _actionsPadding;

  /// Used to configure the [IconTheme] for the [AlertDialog.icon] widget.
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.iconColor] property in [data] instead.
  Color? get iconColor => _data != null ? _data.iconColor : _iconColor;

  /// Overrides the default value for [barrierColor] in [showDialog].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.barrierColor] property in [data] instead.
  Color? get barrierColor => _data != null ? _data.barrierColor : _barrierColor;

  /// Overrides the default value for [Dialog.insetPadding].
  EdgeInsets? get insetPadding => _data != null ? _data.insetPadding : _insetPadding;

  /// Overrides the default value of [Dialog.clipBehavior].
  ///
  /// This property is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.clipBehavior] property in [data] instead.
  Clip? get clipBehavior => _data != null ? _data.clipBehavior : _clipBehavior;

  /// The properties used for all descendant [Dialog] widgets.
  DialogThemeData get data {
    return _data ??
        DialogThemeData(
          backgroundColor: _backgroundColor,
          elevation: _elevation,
          shadowColor: _shadowColor,
          surfaceTintColor: _surfaceTintColor,
          shape: _shape,
          alignment: _alignment,
          iconColor: _iconColor,
          titleTextStyle: _titleTextStyle,
          contentTextStyle: _contentTextStyle,
          actionsPadding: _actionsPadding,
          barrierColor: _barrierColor,
          insetPadding: _insetPadding,
          clipBehavior: _clipBehavior,
        );
  }

  /// Retrieves the [DialogThemeData] from the closest ancestor [DialogTheme].
  ///
  /// If there is no enclosing [DialogTheme] widget, then
  /// [ThemeData.dialogTheme] is used.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// DialogThemeData theme = DialogTheme.of(context);
  /// ```
  static DialogThemeData of(BuildContext context) {
    final DialogTheme? dialogTheme = context.dependOnInheritedWidgetOfExactType<DialogTheme>();
    return dialogTheme?.data ?? Theme.of(context).dialogTheme;
  }

  @override
  Widget wrap(BuildContext context, Widget child) {
    return DialogTheme(data: data, child: child);
  }

  @override
  bool updateShouldNotify(DialogTheme oldWidget) => data != oldWidget.data;

  /// Creates a copy of this object but with the given fields replaced with the
  /// new values.
  ///
  /// This method is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.copyWith] instead.
  DialogTheme copyWith({
    Color? backgroundColor,
    double? elevation,
    Color? shadowColor,
    Color? surfaceTintColor,
    ShapeBorder? shape,
    AlignmentGeometry? alignment,
    Color? iconColor,
    TextStyle? titleTextStyle,
    TextStyle? contentTextStyle,
    EdgeInsetsGeometry? actionsPadding,
    Color? barrierColor,
    EdgeInsets? insetPadding,
    Clip? clipBehavior,
  }) {
    return DialogTheme(
      backgroundColor: backgroundColor ?? this.backgroundColor,
      elevation: elevation ?? this.elevation,
      shadowColor: shadowColor ?? this.shadowColor,
      surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
      shape: shape ?? this.shape,
      alignment: alignment ?? this.alignment,
      iconColor: iconColor ?? this.iconColor,
      titleTextStyle: titleTextStyle ?? this.titleTextStyle,
      contentTextStyle: contentTextStyle ?? this.contentTextStyle,
      actionsPadding: actionsPadding ?? this.actionsPadding,
      barrierColor: barrierColor ?? this.barrierColor,
      insetPadding: insetPadding ?? this.insetPadding,
      clipBehavior: clipBehavior ?? this.clipBehavior,
    );
  }

  /// Linearly interpolate between two dialog themes.
  ///
  /// {@macro dart.ui.shadow.lerp}
  ///
  /// This method is obsolete and will be deprecated in a future release:
  /// please use the [DialogThemeData.lerp] instead.
  static DialogTheme lerp(DialogTheme? a, DialogTheme? b, double t) {
    if (identical(a, b) && a != null) {
      return a;
    }
    return DialogTheme(
      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
      elevation: lerpDouble(a?.elevation, b?.elevation, t),
      shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
      surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
      alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
      iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
      titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
      contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
      actionsPadding: EdgeInsetsGeometry.lerp(a?.actionsPadding, b?.actionsPadding, t),
      barrierColor: Color.lerp(a?.barrierColor, b?.barrierColor, t),
      insetPadding: EdgeInsets.lerp(a?.insetPadding, b?.insetPadding, t),
      clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
    properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
    properties.add(
      DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null),
    );
    properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
    properties.add(
      DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null),
    );
    properties.add(
      DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null),
    );
    properties.add(
      DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null),
    );
    properties.add(ColorProperty('barrierColor', barrierColor, defaultValue: null));
    properties.add(
      DiagnosticsProperty<EdgeInsets>('insetPadding', insetPadding, defaultValue: null),
    );
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
  }
}

/// Defines default property values for descendant [Dialog] widgets.
///
/// Descendant widgets obtain the current [DialogThemeData] object using
/// [DialogTheme.of]. Instances of [DialogThemeData] can be
/// customized with [DialogThemeData.copyWith].
///
/// Typically a [DialogThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.dialogTheme].
///
/// All [DialogThemeData] properties are `null` by default. When null, the [Dialog]
/// will use the values from [ThemeData] if they exist, otherwise it will
/// provide its own defaults. See the individual [Dialog] properties for details.
///
/// See also:
///
///  * [Dialog], a dialog that can be customized using this [DialogTheme].
///  * [AlertDialog], a dialog that can be customized using this [DialogTheme].
///  * [SimpleDialog], a dialog that can be customized using this [DialogTheme].
///  * [ThemeData], which describes the overall theme information for the
///    application.
@immutable
class DialogThemeData with Diagnosticable {
  /// Creates a dialog theme that can be used for [ThemeData.dialogTheme].
  const DialogThemeData({
    this.backgroundColor,
    this.elevation,
    this.shadowColor,
    this.surfaceTintColor,
    this.shape,
    this.alignment,
    this.iconColor,
    this.titleTextStyle,
    this.contentTextStyle,
    this.actionsPadding,
    this.barrierColor,
    this.insetPadding,
    this.clipBehavior,
    this.constraints,
  });

  /// Overrides the default value for [Dialog.backgroundColor].
  final Color? backgroundColor;

  /// Overrides the default value for [Dialog.elevation].
  final double? elevation;

  /// Overrides the default value for [Dialog.shadowColor].
  final Color? shadowColor;

  /// Overrides the default value for [Dialog.surfaceTintColor].
  final Color? surfaceTintColor;

  /// Overrides the default value for [Dialog.shape].
  final ShapeBorder? shape;

  /// Overrides the default value for [Dialog.alignment].
  final AlignmentGeometry? alignment;

  /// Overrides the default value for [DefaultTextStyle] for [SimpleDialog.title] and
  /// [AlertDialog.title].
  final TextStyle? titleTextStyle;

  /// Overrides the default value for [DefaultTextStyle] for [SimpleDialog.children] and
  /// [AlertDialog.content].
  final TextStyle? contentTextStyle;

  /// Overrides the default value for [AlertDialog.actionsPadding].
  final EdgeInsetsGeometry? actionsPadding;

  /// Used to configure the [IconTheme] for the [AlertDialog.icon] widget.
  final Color? iconColor;

  /// Overrides the default value for [barrierColor] in [showDialog].
  final Color? barrierColor;

  /// Overrides the default value for [Dialog.insetPadding].
  final EdgeInsets? insetPadding;

  /// Overrides the default value of [Dialog.clipBehavior].
  final Clip? clipBehavior;

  /// Constrains the size of the [Dialog].
  ///
  /// If null, the bottom sheet's size will be unconstrained.
  final BoxConstraints? constraints;

  /// Creates a copy of this object but with the given fields replaced with the
  /// new values.
  DialogThemeData copyWith({
    Color? backgroundColor,
    double? elevation,
    Color? shadowColor,
    Color? surfaceTintColor,
    ShapeBorder? shape,
    AlignmentGeometry? alignment,
    Color? iconColor,
    TextStyle? titleTextStyle,
    TextStyle? contentTextStyle,
    EdgeInsetsGeometry? actionsPadding,
    Color? barrierColor,
    EdgeInsets? insetPadding,
    Clip? clipBehavior,
    BoxConstraints? constraints,
  }) {
    return DialogThemeData(
      backgroundColor: backgroundColor ?? this.backgroundColor,
      elevation: elevation ?? this.elevation,
      shadowColor: shadowColor ?? this.shadowColor,
      surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
      shape: shape ?? this.shape,
      alignment: alignment ?? this.alignment,
      iconColor: iconColor ?? this.iconColor,
      titleTextStyle: titleTextStyle ?? this.titleTextStyle,
      contentTextStyle: contentTextStyle ?? this.contentTextStyle,
      actionsPadding: actionsPadding ?? this.actionsPadding,
      barrierColor: barrierColor ?? this.barrierColor,
      insetPadding: insetPadding ?? this.insetPadding,
      clipBehavior: clipBehavior ?? this.clipBehavior,
      constraints: constraints ?? this.constraints,
    );
  }

  /// Linearly interpolate between two [DialogThemeData].
  ///
  /// {@macro dart.ui.shadow.lerp}
  static DialogThemeData lerp(DialogThemeData? a, DialogThemeData? b, double t) {
    if (identical(a, b) && a != null) {
      return a;
    }
    return DialogThemeData(
      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
      elevation: lerpDouble(a?.elevation, b?.elevation, t),
      shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
      surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
      alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
      iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
      titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
      contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
      actionsPadding: EdgeInsetsGeometry.lerp(a?.actionsPadding, b?.actionsPadding, t),
      barrierColor: Color.lerp(a?.barrierColor, b?.barrierColor, t),
      insetPadding: EdgeInsets.lerp(a?.insetPadding, b?.insetPadding, t),
      clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
      constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t),
    );
  }

  @override
  int get hashCode => Object.hashAll(<Object?>[
    backgroundColor,
    elevation,
    shadowColor,
    surfaceTintColor,
    shape,
    alignment,
    iconColor,
    titleTextStyle,
    contentTextStyle,
    actionsPadding,
    barrierColor,
    insetPadding,
    clipBehavior,
    constraints,
  ]);

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is DialogThemeData &&
        other.backgroundColor == backgroundColor &&
        other.elevation == elevation &&
        other.shadowColor == shadowColor &&
        other.surfaceTintColor == surfaceTintColor &&
        other.shape == shape &&
        other.alignment == alignment &&
        other.iconColor == iconColor &&
        other.titleTextStyle == titleTextStyle &&
        other.contentTextStyle == contentTextStyle &&
        other.actionsPadding == actionsPadding &&
        other.barrierColor == barrierColor &&
        other.insetPadding == insetPadding &&
        other.clipBehavior == clipBehavior &&
        other.constraints == constraints;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
    properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
    properties.add(
      DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null),
    );
    properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
    properties.add(
      DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null),
    );
    properties.add(
      DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null),
    );
    properties.add(
      DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null),
    );
    properties.add(ColorProperty('barrierColor', barrierColor, defaultValue: null));
    properties.add(
      DiagnosticsProperty<EdgeInsets>('insetPadding', insetPadding, defaultValue: null),
    );
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
    properties.add(
      DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null),
    );
  }
}
