// 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.

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

import 'theme.dart';

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

/// Used with [ExpansionTileTheme] to define default property values for
/// descendant [ExpansionTile] widgets.
///
/// Descendant widgets obtain the current [ExpansionTileThemeData] object
/// using `ExpansionTileTheme.of(context)`. Instances of
/// [ExpansionTileThemeData] can be customized with
/// [ExpansionTileThemeData.copyWith].
///
/// A [ExpansionTileThemeData] is often specified as part of the
/// overall [Theme] with [ThemeData.expansionTileTheme].
///
/// All [ExpansionTileThemeData] properties are `null` by default.
/// When a theme property is null, the [ExpansionTile]  will provide its own
/// default based on the overall [Theme]'s textTheme and
/// colorScheme. See the individual [ExpansionTile] properties for details.
///
/// See also:
///
///  * [ThemeData], which describes the overall theme information for the
///    application.
///  * [ExpansionTileTheme] which overrides the default [ExpansionTileTheme]
///    of its [ExpansionTile] descendants.
///  * [ThemeData.textTheme], text with a color that contrasts with the card
///    and canvas colors.
///  * [ThemeData.colorScheme], the thirteen colors that most Material widget
///    default colors are based on.
@immutable
class ExpansionTileThemeData with Diagnosticable {
  /// Creates a [ExpansionTileThemeData].
  const ExpansionTileThemeData ({
    this.backgroundColor,
    this.collapsedBackgroundColor,
    this.tilePadding,
    this.expandedAlignment,
    this.childrenPadding,
    this.iconColor,
    this.collapsedIconColor,
    this.textColor,
    this.collapsedTextColor,
    this.shape,
    this.collapsedShape,
    this.clipBehavior,
  });

  /// Overrides the default value of [ExpansionTile.backgroundColor].
  final Color? backgroundColor;

  /// Overrides the default value of [ExpansionTile.collapsedBackgroundColor].
  final Color? collapsedBackgroundColor;

  /// Overrides the default value of [ExpansionTile.tilePadding].
  final EdgeInsetsGeometry? tilePadding;

  /// Overrides the default value of [ExpansionTile.expandedAlignment].
  final AlignmentGeometry? expandedAlignment;

  /// Overrides the default value of [ExpansionTile.childrenPadding].
  final EdgeInsetsGeometry? childrenPadding;

  /// Overrides the default value of [ExpansionTile.iconColor].
  final Color? iconColor;

  /// Overrides the default value of [ExpansionTile.collapsedIconColor].
  final Color? collapsedIconColor;

  /// Overrides the default value of [ExpansionTile.textColor].
  final Color? textColor;

  /// Overrides the default value of [ExpansionTile.collapsedTextColor].
  final Color? collapsedTextColor;

  /// Overrides the default value of [ExpansionTile.shape].
  final ShapeBorder? shape;

  /// Overrides the default value of [ExpansionTile.collapsedShape].
  final ShapeBorder? collapsedShape;

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

  /// Creates a copy of this object with the given fields replaced with the
  /// new values.
  ExpansionTileThemeData copyWith({
    Color? backgroundColor,
    Color? collapsedBackgroundColor,
    EdgeInsetsGeometry? tilePadding,
    AlignmentGeometry? expandedAlignment,
    EdgeInsetsGeometry? childrenPadding,
    Color? iconColor,
    Color? collapsedIconColor,
    Color? textColor,
    Color? collapsedTextColor,
    ShapeBorder? shape,
    ShapeBorder? collapsedShape,
    Clip? clipBehavior,
  }) {
    return ExpansionTileThemeData(
      backgroundColor: backgroundColor ?? this.backgroundColor,
      collapsedBackgroundColor: collapsedBackgroundColor ?? this.collapsedBackgroundColor,
      tilePadding: tilePadding ?? this.tilePadding,
      expandedAlignment: expandedAlignment ?? this.expandedAlignment,
      childrenPadding: childrenPadding ?? this.childrenPadding,
      iconColor: iconColor ?? this.iconColor,
      collapsedIconColor: collapsedIconColor ?? this.collapsedIconColor,
      textColor: textColor ?? this.textColor,
      collapsedTextColor: collapsedTextColor ?? this.collapsedTextColor,
      shape: shape ?? this.shape,
      collapsedShape: collapsedShape ?? this.collapsedShape,
      clipBehavior: clipBehavior ?? this.clipBehavior,
    );
  }

  /// Linearly interpolate between ExpansionTileThemeData objects.
  static ExpansionTileThemeData? lerp(ExpansionTileThemeData? a, ExpansionTileThemeData? b, double t) {
    assert (t != null);
    if (a == null && b == null) {
      return null;
    }
    return ExpansionTileThemeData(
      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
      collapsedBackgroundColor: Color.lerp(a?.collapsedBackgroundColor, b?.collapsedBackgroundColor, t),
      tilePadding: EdgeInsetsGeometry.lerp(a?.tilePadding, b?.tilePadding, t),
      expandedAlignment: AlignmentGeometry.lerp(a?.expandedAlignment, b?.expandedAlignment, t),
      childrenPadding: EdgeInsetsGeometry.lerp(a?.childrenPadding, b?.childrenPadding, t),
      iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
      collapsedIconColor: Color.lerp(a?.collapsedIconColor, b?.collapsedIconColor, t),
      textColor: Color.lerp(a?.textColor, b?.textColor, t),
      collapsedTextColor: Color.lerp(a?.collapsedTextColor, b?.collapsedTextColor, t),
      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
      collapsedShape: ShapeBorder.lerp(a?.collapsedShape, b?.collapsedShape, t),
    );
  }

  @override
  int get hashCode {
    return Object.hash(
      backgroundColor,
      collapsedBackgroundColor,
      tilePadding,
      expandedAlignment,
      childrenPadding,
      iconColor,
      collapsedIconColor,
      textColor,
      collapsedTextColor,
      shape,
      collapsedShape,
      clipBehavior,
    );
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is ExpansionTileThemeData
      && other.backgroundColor == backgroundColor
      && other.collapsedBackgroundColor == collapsedBackgroundColor
      && other.tilePadding == tilePadding
      && other.expandedAlignment == expandedAlignment
      && other.childrenPadding == childrenPadding
      && other.iconColor == iconColor
      && other.collapsedIconColor == collapsedIconColor
      && other.textColor == textColor
      && other.collapsedTextColor == collapsedTextColor
      && other.shape == shape
      && other.collapsedShape == collapsedShape
      && other.clipBehavior == clipBehavior;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
    properties.add(ColorProperty('collapsedBackgroundColor', collapsedBackgroundColor, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('tilePadding', tilePadding, defaultValue: null));
    properties.add(DiagnosticsProperty<AlignmentGeometry>('expandedAlignment', expandedAlignment, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('childrenPadding', childrenPadding, defaultValue: null));
    properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
    properties.add(ColorProperty('collapsedIconColor', collapsedIconColor, defaultValue: null));
    properties.add(ColorProperty('textColor', textColor, defaultValue: null));
    properties.add(ColorProperty('collapsedTextColor', collapsedTextColor, defaultValue: null));
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
    properties.add(DiagnosticsProperty<ShapeBorder>('collapsedShape', collapsedShape, defaultValue: null));
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
  }
}

/// Overrides the default [ExpansionTileTheme] of its [ExpansionTile] descendants.
///
/// See also:
///
///  * [ExpansionTileThemeData], which is used to configure this theme.
///  * [ThemeData.expansionTileTheme], which can be used to override the default
///    [ExpansionTileTheme] for [ExpansionTile]s below the overall [Theme].
class ExpansionTileTheme extends InheritedTheme {
  /// Applies the given theme [data] to [child].
  ///
  /// The [data] and [child] arguments must not be null.
  const ExpansionTileTheme({
    super.key,
    required this.data,
    required super.child,
  }) : assert(child != null),
       assert(data != null);

  /// Specifies color, alignment, and text style values for
  /// descendant [ExpansionTile] widgets.
  final ExpansionTileThemeData data;

  /// The closest instance of this class that encloses the given context.
  ///
  /// If there is no enclosing [ExpansionTileTheme] widget, then
  /// [ThemeData.expansionTileTheme] is used.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// ExpansionTileThemeData theme = ExpansionTileTheme.of(context);
  /// ```
  static ExpansionTileThemeData of(BuildContext context) {
    final ExpansionTileTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<ExpansionTileTheme>();
    return inheritedTheme?.data ?? Theme.of(context).expansionTileTheme;
  }

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

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