// 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 'data_table.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 default property values for descendant [DataTable]
/// widgets.
///
/// Descendant widgets obtain the current [DataTableThemeData] object
/// using [DataTableTheme.of]. Instances of [DataTableThemeData] can
/// be customized with [DataTableThemeData.copyWith].
///
/// Typically a [DataTableThemeData] is specified as part of the
/// overall [Theme] with [ThemeData.dataTableTheme].
///
/// All [DataTableThemeData] properties are `null` by default. When
/// null, the [DataTable] will use the values from [ThemeData] if they exist,
/// otherwise it will provide its own defaults based on the overall [Theme]'s
/// textTheme and colorScheme. See the individual [DataTable] properties for
/// details.
///
/// See also:
///
///  * [ThemeData], which describes the overall theme information for the
///    application.
@immutable
class DataTableThemeData with Diagnosticable {
  /// Creates a theme that can be used for [ThemeData.dataTableTheme].
  const DataTableThemeData({
    this.decoration,
    this.dataRowColor,
    @Deprecated(
      'Migrate to use dataRowMinHeight and dataRowMaxHeight instead. '
      'This feature was deprecated after v3.7.0-5.0.pre.',
    )
    double? dataRowHeight,
    double? dataRowMinHeight,
    double? dataRowMaxHeight,
    this.dataTextStyle,
    this.headingRowColor,
    this.headingRowHeight,
    this.headingTextStyle,
    this.horizontalMargin,
    this.columnSpacing,
    this.dividerThickness,
    this.checkboxHorizontalMargin,
    this.headingCellCursor,
    this.dataRowCursor,
    this.headingRowAlignment,
  }) : assert(
         dataRowMinHeight == null ||
             dataRowMaxHeight == null ||
             dataRowMaxHeight >= dataRowMinHeight,
       ),
       assert(
         dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
         'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.',
       ),
       dataRowMinHeight = dataRowHeight ?? dataRowMinHeight,
       dataRowMaxHeight = dataRowHeight ?? dataRowMaxHeight;

  /// {@macro flutter.material.dataTable.decoration}
  final Decoration? decoration;

  /// {@macro flutter.material.dataTable.dataRowColor}
  /// {@macro flutter.material.DataTable.dataRowColor}
  final WidgetStateProperty<Color?>? dataRowColor;

  /// {@macro flutter.material.dataTable.dataRowHeight}
  @Deprecated(
    'Migrate to use dataRowMinHeight and dataRowMaxHeight instead. '
    'This feature was deprecated after v3.7.0-5.0.pre.',
  )
  double? get dataRowHeight => dataRowMinHeight == dataRowMaxHeight ? dataRowMinHeight : null;

  /// {@macro flutter.material.dataTable.dataRowMinHeight}
  final double? dataRowMinHeight;

  /// {@macro flutter.material.dataTable.dataRowMaxHeight}
  final double? dataRowMaxHeight;

  /// {@macro flutter.material.dataTable.dataTextStyle}
  final TextStyle? dataTextStyle;

  /// {@macro flutter.material.dataTable.headingRowColor}
  /// {@macro flutter.material.DataTable.headingRowColor}
  final WidgetStateProperty<Color?>? headingRowColor;

  /// {@macro flutter.material.dataTable.headingRowHeight}
  final double? headingRowHeight;

  /// {@macro flutter.material.dataTable.headingTextStyle}
  final TextStyle? headingTextStyle;

  /// {@macro flutter.material.dataTable.horizontalMargin}
  final double? horizontalMargin;

  /// {@macro flutter.material.dataTable.columnSpacing}
  final double? columnSpacing;

  /// {@macro flutter.material.dataTable.dividerThickness}
  final double? dividerThickness;

  /// {@macro flutter.material.dataTable.checkboxHorizontalMargin}
  final double? checkboxHorizontalMargin;

  /// If specified, overrides the default value of [DataColumn.mouseCursor].
  final WidgetStateProperty<MouseCursor?>? headingCellCursor;

  /// If specified, overrides the default value of [DataRow.mouseCursor].
  final WidgetStateProperty<MouseCursor?>? dataRowCursor;

  /// If specified, overrides the default value of [DataColumn.headingRowAlignment].
  final MainAxisAlignment? headingRowAlignment;

  /// Creates a copy of this object but with the given fields replaced with the
  /// new values.
  DataTableThemeData copyWith({
    Decoration? decoration,
    WidgetStateProperty<Color?>? dataRowColor,
    @Deprecated(
      'Migrate to use dataRowMinHeight and dataRowMaxHeight instead. '
      'This feature was deprecated after v3.7.0-5.0.pre.',
    )
    double? dataRowHeight,
    double? dataRowMinHeight,
    double? dataRowMaxHeight,
    TextStyle? dataTextStyle,
    WidgetStateProperty<Color?>? headingRowColor,
    double? headingRowHeight,
    TextStyle? headingTextStyle,
    double? horizontalMargin,
    double? columnSpacing,
    double? dividerThickness,
    double? checkboxHorizontalMargin,
    WidgetStateProperty<MouseCursor?>? headingCellCursor,
    WidgetStateProperty<MouseCursor?>? dataRowCursor,
    MainAxisAlignment? headingRowAlignment,
  }) {
    assert(
      dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
      'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.',
    );
    dataRowMinHeight = dataRowHeight ?? dataRowMinHeight;
    dataRowMaxHeight = dataRowHeight ?? dataRowMaxHeight;

    return DataTableThemeData(
      decoration: decoration ?? this.decoration,
      dataRowColor: dataRowColor ?? this.dataRowColor,
      dataRowMinHeight: dataRowMinHeight ?? this.dataRowMinHeight,
      dataRowMaxHeight: dataRowMaxHeight ?? this.dataRowMaxHeight,
      dataTextStyle: dataTextStyle ?? this.dataTextStyle,
      headingRowColor: headingRowColor ?? this.headingRowColor,
      headingRowHeight: headingRowHeight ?? this.headingRowHeight,
      headingTextStyle: headingTextStyle ?? this.headingTextStyle,
      horizontalMargin: horizontalMargin ?? this.horizontalMargin,
      columnSpacing: columnSpacing ?? this.columnSpacing,
      dividerThickness: dividerThickness ?? this.dividerThickness,
      checkboxHorizontalMargin: checkboxHorizontalMargin ?? this.checkboxHorizontalMargin,
      headingCellCursor: headingCellCursor ?? this.headingCellCursor,
      dataRowCursor: dataRowCursor ?? this.dataRowCursor,
      headingRowAlignment: headingRowAlignment ?? this.headingRowAlignment,
    );
  }

  /// Linearly interpolate between two [DataTableThemeData]s.
  ///
  /// {@macro dart.ui.shadow.lerp}
  static DataTableThemeData lerp(DataTableThemeData a, DataTableThemeData b, double t) {
    if (identical(a, b)) {
      return a;
    }
    return DataTableThemeData(
      decoration: Decoration.lerp(a.decoration, b.decoration, t),
      dataRowColor: WidgetStateProperty.lerp<Color?>(a.dataRowColor, b.dataRowColor, t, Color.lerp),
      dataRowMinHeight: lerpDouble(a.dataRowMinHeight, b.dataRowMinHeight, t),
      dataRowMaxHeight: lerpDouble(a.dataRowMaxHeight, b.dataRowMaxHeight, t),
      dataTextStyle: TextStyle.lerp(a.dataTextStyle, b.dataTextStyle, t),
      headingRowColor: WidgetStateProperty.lerp<Color?>(
        a.headingRowColor,
        b.headingRowColor,
        t,
        Color.lerp,
      ),
      headingRowHeight: lerpDouble(a.headingRowHeight, b.headingRowHeight, t),
      headingTextStyle: TextStyle.lerp(a.headingTextStyle, b.headingTextStyle, t),
      horizontalMargin: lerpDouble(a.horizontalMargin, b.horizontalMargin, t),
      columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t),
      dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t),
      checkboxHorizontalMargin: lerpDouble(
        a.checkboxHorizontalMargin,
        b.checkboxHorizontalMargin,
        t,
      ),
      headingCellCursor: t < 0.5 ? a.headingCellCursor : b.headingCellCursor,
      dataRowCursor: t < 0.5 ? a.dataRowCursor : b.dataRowCursor,
      headingRowAlignment: t < 0.5 ? a.headingRowAlignment : b.headingRowAlignment,
    );
  }

  @override
  int get hashCode => Object.hash(
    decoration,
    dataRowColor,
    dataRowMinHeight,
    dataRowMaxHeight,
    dataTextStyle,
    headingRowColor,
    headingRowHeight,
    headingTextStyle,
    horizontalMargin,
    columnSpacing,
    dividerThickness,
    checkboxHorizontalMargin,
    headingCellCursor,
    dataRowCursor,
    headingRowAlignment,
  );

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is DataTableThemeData &&
        other.decoration == decoration &&
        other.dataRowColor == dataRowColor &&
        other.dataRowMinHeight == dataRowMinHeight &&
        other.dataRowMaxHeight == dataRowMaxHeight &&
        other.dataTextStyle == dataTextStyle &&
        other.headingRowColor == headingRowColor &&
        other.headingRowHeight == headingRowHeight &&
        other.headingTextStyle == headingTextStyle &&
        other.horizontalMargin == horizontalMargin &&
        other.columnSpacing == columnSpacing &&
        other.dividerThickness == dividerThickness &&
        other.checkboxHorizontalMargin == checkboxHorizontalMargin &&
        other.headingCellCursor == headingCellCursor &&
        other.dataRowCursor == dataRowCursor &&
        other.headingRowAlignment == headingRowAlignment;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<Decoration>('decoration', decoration, defaultValue: null));
    properties.add(
      DiagnosticsProperty<WidgetStateProperty<Color?>>(
        'dataRowColor',
        dataRowColor,
        defaultValue: null,
      ),
    );
    properties.add(DoubleProperty('dataRowMinHeight', dataRowMinHeight, defaultValue: null));
    properties.add(DoubleProperty('dataRowMaxHeight', dataRowMaxHeight, defaultValue: null));
    properties.add(
      DiagnosticsProperty<TextStyle>('dataTextStyle', dataTextStyle, defaultValue: null),
    );
    properties.add(
      DiagnosticsProperty<WidgetStateProperty<Color?>>(
        'headingRowColor',
        headingRowColor,
        defaultValue: null,
      ),
    );
    properties.add(DoubleProperty('headingRowHeight', headingRowHeight, defaultValue: null));
    properties.add(
      DiagnosticsProperty<TextStyle>('headingTextStyle', headingTextStyle, defaultValue: null),
    );
    properties.add(DoubleProperty('horizontalMargin', horizontalMargin, defaultValue: null));
    properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null));
    properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null));
    properties.add(
      DoubleProperty('checkboxHorizontalMargin', checkboxHorizontalMargin, defaultValue: null),
    );
    properties.add(
      DiagnosticsProperty<WidgetStateProperty<MouseCursor?>?>(
        'headingCellCursor',
        headingCellCursor,
        defaultValue: null,
      ),
    );
    properties.add(
      DiagnosticsProperty<WidgetStateProperty<MouseCursor?>?>(
        'dataRowCursor',
        dataRowCursor,
        defaultValue: null,
      ),
    );
    properties.add(
      EnumProperty<MainAxisAlignment>(
        'headingRowAlignment',
        headingRowAlignment,
        defaultValue: null,
      ),
    );
  }
}

/// Applies a data table theme to descendant [DataTable] widgets.
///
/// Descendant widgets obtain the current theme's [DataTableTheme] object using
/// [DataTableTheme.of]. When a widget uses [DataTableTheme.of], it is
/// automatically rebuilt if the theme later changes.
///
/// A data table theme can be specified as part of the overall Material
/// theme using [ThemeData.dataTableTheme].
///
/// See also:
///
///  * [DataTableThemeData], which describes the actual configuration
///    of a data table theme.
class DataTableTheme extends InheritedWidget {
  /// Constructs a data table theme that configures all descendant
  /// [DataTable] widgets.
  const DataTableTheme({super.key, required this.data, required super.child});

  /// The properties used for all descendant [DataTable] widgets.
  final DataTableThemeData data;

  /// Returns the configuration [data] from the closest
  /// [DataTableTheme] ancestor. If there is no ancestor, it returns
  /// [ThemeData.dataTableTheme]. Applications can assume that the
  /// returned value will not be null.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// DataTableThemeData theme = DataTableTheme.of(context);
  /// ```
  static DataTableThemeData of(BuildContext context) {
    final DataTableTheme? dataTableTheme = context
        .dependOnInheritedWidgetOfExactType<DataTableTheme>();
    return dataTableTheme?.data ?? Theme.of(context).dataTableTheme;
  }

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