/// Created by Haoyi on 2022/8/25
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

/// Created by Haoyi on 4/15/21

class TextMetrics {
  final double width;
  final double height;
  final int? maxLines;
  final bool didExceedMaxLines;
  final double preferredLineHeight;

  TextMetrics(
      {required this.width,
      required this.height,
      required this.maxLines,
      required this.didExceedMaxLines,
      required this.preferredLineHeight});

  @override
  String toString() {
    return 'TextMetrics{width: $width, height: $height, maxLines: $maxLines, didExceedMaxLines: $didExceedMaxLines, preferredLineHeight: $preferredLineHeight}';
  }
}

class WidgetUtils {
  static Size calculateTextSize(String text, TextStyle style,
      {double? maxWidth, StrutStyle? strutStyle, int maxLine = 1}) {
    final constraints = BoxConstraints(
      maxWidth: maxWidth ?? double.infinity,
      minHeight: 0.0,
      minWidth: maxWidth ?? 0.0,
    );

    final renderParagraph = RenderParagraph(
        TextSpan(
          text: text,
          style: style,
        ),
        maxLines: maxLine,
        strutStyle: strutStyle ?? StrutStyle.fromTextStyle(style),
        textDirection: TextDirection.ltr)
      ..layout(constraints);
    return Size(renderParagraph.getMinIntrinsicWidth(style.fontSize ?? 14.0).ceilToDouble(),
        renderParagraph.getMinIntrinsicHeight(style.fontSize ?? 14.0).ceilToDouble());
  }

  static TextMetrics measureText(String text, TextStyle style,
      {double? maxWidth, StrutStyle? strutStyle, int maxLines = 1}) {
    TextPainter painter = TextPainter(
        maxLines: maxLines,
        textDirection: TextDirection.ltr,
        strutStyle: strutStyle ?? StrutStyle.fromTextStyle(style),
        text: TextSpan(text: text, style: style));
    painter.layout(maxWidth: maxWidth ?? double.infinity);
    return TextMetrics(
        width: painter.width,
        height: painter.height,
        maxLines: maxLines,
        didExceedMaxLines: painter.didExceedMaxLines,
        preferredLineHeight: painter.preferredLineHeight);
  }

  static Size boundingTextSize(BuildContext context, String text, TextStyle style,
      {int maxLines = 2 ^ 31, double maxWidth = double.infinity, StrutStyle? strutStyle}) {
    if (text.isEmpty) {
      return Size.zero;
    }
    final TextPainter textPainter = TextPainter(
        textDirection: TextDirection.ltr,
        locale: Localizations.localeOf(
          context,
        ),
        text: TextSpan(text: text, style: style),
        strutStyle: strutStyle,
        maxLines: maxLines)
      ..layout(maxWidth: maxWidth);
    return textPainter.size;
  }

  static int calculateMaxLines(BuildContext context, String text, TextStyle style, double maxWidth,
      {TextAlign? textAlign, TextDirection? textDirection, StrutStyle? strutStyle}) {
    int lines = 1;
    late TextPainter textPainter;
    do {
      textPainter = TextPainter(
          textAlign: textAlign ?? TextAlign.left,
          textDirection: textDirection ?? TextDirection.ltr,
          locale: Localizations.localeOf(
            context,
          ),
          text: TextSpan(text: text, style: style),
          strutStyle: strutStyle,
          maxLines: lines);
      textPainter.layout(maxWidth: maxWidth);
      if ((textPainter.didExceedMaxLines || textPainter.width > maxWidth)) {
        lines++;
      }
    } while ((textPainter.didExceedMaxLines || textPainter.width > maxWidth));
    return lines;
  }

  static String breakWord(String word) {
    if (word.isEmpty) {
      return word;
    }
    String breakWord = ' ';
    for (var element in word.runes) {
      breakWord += String.fromCharCode(element);
      breakWord += '\u200B';
    }
    return breakWord;
  }

  static Rect getWidgetBoundary(GlobalKey key, {Offset offset = Offset.zero}) {
    RenderObject? widgetRenderBox = key.currentContext?.findRenderObject();
    if (widgetRenderBox != null) {
      Offset widgetOffset = (widgetRenderBox as RenderBox).localToGlobal(Offset.zero);
      Size size = widgetRenderBox.size;
      final widgetCenter =
          Rect.fromLTWH(widgetOffset.dx, widgetOffset.dy, size.width, size.height).center;
      return Rect.fromCenter(
          center: Offset(widgetCenter.dx + offset.dx, widgetCenter.dy + offset.dy),
          width: size.width,
          height: size.height);
    }
    return Rect.zero;
  }

  static List calculateFontSize(BuildContext context, String text, BoxConstraints size,
      {TextSpan? textSpan,
      TextStyle? textStyle,
      int? maxLines = 1,
      double? textScaleFactor,
      List<double>? presetFontSizeList,
      double minFontSize = 12,
      double maxFontSize = double.infinity,
      double stepGranularity = 1,
      bool wrapWords = true,
      TextAlign? textAlign,
      TextDirection? textDirection,
      Locale? locale,
      StrutStyle? strutStyle}) {
    final span = TextSpan(
      style: textSpan?.style ?? textStyle,
      text: textSpan?.text ?? text,
      children: textSpan?.children,
      recognizer: textSpan?.recognizer,
    );

    final userScale = textScaleFactor ?? MediaQuery.textScaleFactorOf(context);

    int left;
    int right;
    final defaultTextStyle = DefaultTextStyle.of(context);

    var style = textStyle;
    if (textStyle == null || textStyle.inherit) {
      style = defaultTextStyle.style.merge(textStyle);
    }
    if (style!.fontSize == null) {
      style = style.copyWith(fontSize: 14);
    }

    final presetFontSizes = presetFontSizeList?.reversed.toList();
    if (presetFontSizes == null) {
      final num defaultFontSize = style.fontSize!.clamp(minFontSize, maxFontSize);
      final defaultScale = defaultFontSize * userScale / style.fontSize!;
      if (checkTextFits(span, defaultScale, maxLines, size,
          wrapWords: wrapWords,
          textAlign: textAlign,
          textDirection: textDirection,
          locale: locale,
          strutStyle: strutStyle)) {
        return <Object>[defaultFontSize * userScale, true];
      }

      left = (minFontSize / stepGranularity).floor();
      right = (defaultFontSize / stepGranularity).ceil();
    } else {
      left = 0;
      right = presetFontSizes.length - 1;
    }

    var lastValueFits = false;
    while (left <= right) {
      final mid = (left + (right - left) / 2).floor();
      double scale;
      if (presetFontSizes == null) {
        scale = mid * userScale * stepGranularity / style.fontSize!;
      } else {
        scale = presetFontSizes[mid] * userScale / style.fontSize!;
      }
      if (checkTextFits(span, scale, maxLines, size,
          wrapWords: wrapWords,
          textAlign: textAlign,
          textDirection: textDirection,
          locale: locale,
          strutStyle: strutStyle)) {
        left = mid + 1;
        lastValueFits = true;
      } else {
        right = mid - 1;
      }
    }

    if (!lastValueFits) {
      right += 1;
    }

    double fontSize;
    if (presetFontSizes == null) {
      fontSize = right * userScale * stepGranularity;
    } else {
      fontSize = presetFontSizes[right] * userScale;
    }

    return <Object>[fontSize, lastValueFits];
  }

  static bool checkTextFits(TextSpan text, double scale, int? maxLines, BoxConstraints constraints,
      {bool wrapWords = true,
      TextAlign? textAlign,
      TextDirection? textDirection,
      Locale? locale,
      StrutStyle? strutStyle}) {
    if (!wrapWords) {
      final words = text.toPlainText().split(RegExp('\\s+'));

      final wordWrapTextPainter = TextPainter(
        text: TextSpan(
          style: text.style,
          text: words.join('\n'),
        ),
        textAlign: textAlign ?? TextAlign.left,
        textDirection: textDirection ?? TextDirection.ltr,
        textScaleFactor: scale,
        maxLines: words.length,
        locale: locale,
        strutStyle: strutStyle,
      );

      wordWrapTextPainter.layout(maxWidth: constraints.maxWidth);

      if (wordWrapTextPainter.didExceedMaxLines ||
          wordWrapTextPainter.width > constraints.maxWidth) {
        return false;
      }
    }

    final textPainter = TextPainter(
      text: text,
      textAlign: textAlign ?? TextAlign.left,
      textDirection: textDirection ?? TextDirection.ltr,
      textScaleFactor: scale,
      maxLines: maxLines,
      locale: locale,
      strutStyle: strutStyle,
    );

    textPainter.layout(maxWidth: constraints.maxWidth);

    return !(textPainter.didExceedMaxLines ||
        textPainter.height > constraints.maxHeight ||
        textPainter.width > constraints.maxWidth);
  }

  static List<Widget> separate(List<Widget> widgets, Widget? separator) {
    if (separator == null) {
      return widgets;
    }
    final List<Widget> separatedList = [];

    for (int i = 0; i < widgets.length; i++) {
      separatedList.add(widgets[i]);

      if (i != widgets.length - 1) {
        separatedList.add(separator);
      }
    }

    return separatedList;
  }
}
