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

import 'dart:math' as math;

import 'package:flutter/foundation.dart';

import 'basic.dart';
import 'debug.dart';
import 'framework.dart';

const double _kOffset = 40.0; // distance to bottom of banner, at a 45 degree angle inwards
const double _kHeight = 12.0; // height of banner
const double _kBottomOffset = _kOffset + math.sqrt1_2 * _kHeight;
const Rect _kRect = Rect.fromLTWH(-_kOffset, _kOffset - _kHeight, _kOffset * 2.0, _kHeight);
const BoxShadow _kShadow = BoxShadow(color: Color(0x7F000000), blurRadius: 6.0);

const Color _kColor = Color(0xA0B71C1C);
const TextStyle _kTextStyle = TextStyle(
  color: Color(0xFFFFFFFF),
  fontSize: _kHeight * 0.85,
  fontWeight: FontWeight.w900,
  height: 1.0,
);

/// Where to show a [Banner].
///
/// The start and end locations are relative to the ambient [Directionality]
/// (which can be overridden by [Banner.layoutDirection]).
enum BannerLocation {
  /// Show the banner in the top-right corner when the ambient [Directionality]
  /// (or [Banner.layoutDirection]) is [TextDirection.rtl] and in the top-left
  /// corner when the ambient [Directionality] is [TextDirection.ltr].
  topStart,

  /// Show the banner in the top-left corner when the ambient [Directionality]
  /// (or [Banner.layoutDirection]) is [TextDirection.rtl] and in the top-right
  /// corner when the ambient [Directionality] is [TextDirection.ltr].
  topEnd,

  /// Show the banner in the bottom-right corner when the ambient
  /// [Directionality] (or [Banner.layoutDirection]) is [TextDirection.rtl] and
  /// in the bottom-left corner when the ambient [Directionality] is
  /// [TextDirection.ltr].
  bottomStart,

  /// Show the banner in the bottom-left corner when the ambient
  /// [Directionality] (or [Banner.layoutDirection]) is [TextDirection.rtl] and
  /// in the bottom-right corner when the ambient [Directionality] is
  /// [TextDirection.ltr].
  bottomEnd,
}

/// Paints a [Banner].
class BannerPainter extends CustomPainter {
  /// Creates a banner painter.
  BannerPainter({
    required this.message,
    required this.textDirection,
    required this.location,
    required this.layoutDirection,
    this.color = _kColor,
    this.textStyle = _kTextStyle,
    this.shadow = _kShadow,
  }) : super(repaint: PaintingBinding.instance.systemFonts) {
    assert(debugMaybeDispatchCreated('widgets', 'BannerPainter', this));
  }

  /// The message to show in the banner.
  final String message;

  /// The directionality of the text.
  ///
  /// This value is used to disambiguate how to render bidirectional text. For
  /// example, if the message is an English phrase followed by a Hebrew phrase,
  /// in a [TextDirection.ltr] context the English phrase will be on the left
  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
  /// context, the English phrase will be on the right and the Hebrew phrase on
  /// its left.
  ///
  /// See also:
  ///
  ///  * [layoutDirection], which controls the interpretation of values in
  ///    [location].
  final TextDirection textDirection;

  /// Where to show the banner (e.g., the upper right corner).
  final BannerLocation location;

  /// The directionality of the layout.
  ///
  /// This value is used to interpret the [location] of the banner.
  ///
  /// See also:
  ///
  ///  * [textDirection], which controls the reading direction of the [message].
  final TextDirection layoutDirection;

  /// The color to paint behind the [message].
  ///
  /// Defaults to a dark red.
  final Color color;

  /// The text style to use for the [message].
  ///
  /// Defaults to bold, white text.
  final TextStyle textStyle;

  /// The shadow properties for the banner.
  ///
  /// Use a [BoxShadow] object to define the shadow's color, blur radius,
  /// and spread radius. These properties can be used to create different
  /// shadow effects.
  final BoxShadow shadow;

  bool _prepared = false;
  TextPainter? _textPainter;
  late Paint _paintShadow;
  late Paint _paintBanner;

  /// Release resources held by this painter.
  ///
  /// After calling this method, this object is no longer usable.
  void dispose() {
    assert(debugMaybeDispatchDisposed(this));
    _textPainter?.dispose();
    _textPainter = null;
  }

  void _prepare() {
    _paintShadow = shadow.toPaint();
    _paintBanner = Paint()..color = color;
    _textPainter?.dispose();
    _textPainter = TextPainter(
      text: TextSpan(style: textStyle, text: message),
      textAlign: TextAlign.center,
      textDirection: textDirection,
    );
    _prepared = true;
  }

  @override
  void paint(Canvas canvas, Size size) {
    if (!_prepared) {
      _prepare();
    }
    canvas
      ..translate(_translationX(size.width), _translationY(size.height))
      ..rotate(_rotation)
      ..drawRect(_kRect, _paintShadow)
      ..drawRect(_kRect, _paintBanner);
    const double width = _kOffset * 2.0;
    _textPainter!.layout(minWidth: width, maxWidth: width);
    _textPainter!.paint(
      canvas,
      _kRect.topLeft + Offset(0.0, (_kRect.height - _textPainter!.height) / 2.0),
    );
  }

  @override
  bool shouldRepaint(BannerPainter oldDelegate) {
    return message != oldDelegate.message ||
        location != oldDelegate.location ||
        color != oldDelegate.color ||
        textStyle != oldDelegate.textStyle;
  }

  @override
  bool hitTest(Offset position) => false;

  double _translationX(double width) {
    return switch ((layoutDirection, location)) {
      (TextDirection.rtl, BannerLocation.topStart) => width,
      (TextDirection.ltr, BannerLocation.topStart) => 0.0,
      (TextDirection.rtl, BannerLocation.topEnd) => 0.0,
      (TextDirection.ltr, BannerLocation.topEnd) => width,
      (TextDirection.rtl, BannerLocation.bottomStart) => width - _kBottomOffset,
      (TextDirection.ltr, BannerLocation.bottomStart) => _kBottomOffset,
      (TextDirection.rtl, BannerLocation.bottomEnd) => _kBottomOffset,
      (TextDirection.ltr, BannerLocation.bottomEnd) => width - _kBottomOffset,
    };
  }

  double _translationY(double height) {
    return switch (location) {
      BannerLocation.bottomStart || BannerLocation.bottomEnd => height - _kBottomOffset,
      BannerLocation.topStart || BannerLocation.topEnd => 0.0,
    };
  }

  double get _rotation {
    return math.pi /
        4.0 *
        switch ((layoutDirection, location)) {
          (TextDirection.rtl, BannerLocation.topStart || BannerLocation.bottomEnd) => 1,
          (TextDirection.ltr, BannerLocation.topStart || BannerLocation.bottomEnd) => -1,
          (TextDirection.rtl, BannerLocation.bottomStart || BannerLocation.topEnd) => -1,
          (TextDirection.ltr, BannerLocation.bottomStart || BannerLocation.topEnd) => 1,
        };
  }
}

/// Displays a diagonal message above the corner of another widget.
///
/// Useful for showing the execution mode of an app (e.g., that asserts are
/// enabled.)
///
/// See also:
///
///  * [CheckedModeBanner], which the [WidgetsApp] widget includes by default in
///    debug mode, to show a banner that says "DEBUG".
class Banner extends StatefulWidget {
  /// Creates a banner.
  const Banner({
    super.key,
    this.child,
    required this.message,
    this.textDirection,
    required this.location,
    this.layoutDirection,
    this.color = _kColor,
    this.textStyle = _kTextStyle,
    this.shadow = _kShadow,
  });

  /// The widget to show behind the banner.
  ///
  /// {@macro flutter.widgets.ProxyWidget.child}
  final Widget? child;

  /// The message to show in the banner.
  final String message;

  /// The directionality of the text.
  ///
  /// This is used to disambiguate how to render bidirectional text. For
  /// example, if the message is an English phrase followed by a Hebrew phrase,
  /// in a [TextDirection.ltr] context the English phrase will be on the left
  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
  /// context, the English phrase will be on the right and the Hebrew phrase on
  /// its left.
  ///
  /// Defaults to the ambient [Directionality], if any.
  ///
  /// See also:
  ///
  ///  * [layoutDirection], which controls the interpretation of the [location].
  final TextDirection? textDirection;

  /// Where to show the banner (e.g., the upper right corner).
  final BannerLocation location;

  /// The directionality of the layout.
  ///
  /// This is used to resolve the [location] values.
  ///
  /// Defaults to the ambient [Directionality], if any.
  ///
  /// See also:
  ///
  ///  * [textDirection], which controls the reading direction of the [message].
  final TextDirection? layoutDirection;

  /// The color of the banner.
  final Color color;

  /// The style of the text shown on the banner.
  final TextStyle textStyle;

  /// The shadow properties for the banner.
  ///
  /// Use a [BoxShadow] object to define the shadow's color, blur radius,
  /// and spread radius. These properties can be used to create different
  /// shadow effects.
  final BoxShadow shadow;

  @override
  State<Banner> createState() => _BannerState();
}

class _BannerState extends State<Banner> {
  BannerPainter? _painter;

  @override
  void dispose() {
    _painter?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    assert(
      (widget.textDirection != null && widget.layoutDirection != null) ||
          debugCheckHasDirectionality(context),
    );

    _painter?.dispose();
    _painter = BannerPainter(
      message: widget.message,
      textDirection: widget.textDirection ?? Directionality.of(context),
      location: widget.location,
      layoutDirection: widget.layoutDirection ?? Directionality.of(context),
      color: widget.color,
      textStyle: widget.textStyle,
      shadow: widget.shadow,
    );

    return CustomPaint(foregroundPainter: _painter, child: widget.child);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(StringProperty('message', widget.message, showName: false));
    properties.add(
      EnumProperty<TextDirection>('textDirection', widget.textDirection, defaultValue: null),
    );
    properties.add(EnumProperty<BannerLocation>('location', widget.location));
    properties.add(
      EnumProperty<TextDirection>('layoutDirection', widget.layoutDirection, defaultValue: null),
    );
    properties.add(ColorProperty('color', widget.color, showName: false));
    widget.textStyle.debugFillProperties(properties, prefix: 'text ');
  }
}

/// Displays a [Banner] saying "DEBUG" when running in debug mode.
/// [MaterialApp] builds one of these by default.
///
/// Does nothing in release mode.
class CheckedModeBanner extends StatelessWidget {
  /// Creates a const debug mode banner.
  const CheckedModeBanner({super.key, required this.child});

  /// The widget to show behind the banner.
  ///
  /// {@macro flutter.widgets.ProxyWidget.child}
  final Widget child;

  @override
  Widget build(BuildContext context) {
    Widget result = child;
    assert(() {
      result = Banner(
        message: 'DEBUG',
        textDirection: TextDirection.ltr,
        location: BannerLocation.topEnd,
        child: result,
      );
      return true;
    }());
    return result;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    var message = 'disabled';
    assert(() {
      message = '"DEBUG"';
      return true;
    }());
    properties.add(DiagnosticsNode.message(message));
  }
}
