// 完整 DropCapText 组件，支持 DropCapPosition.bottomRight，支持 \n 换行符自动避让右下角图像

import 'dart:math';
import 'package:flutter/material.dart';

enum DropCapMode {
  inside,
  upwards,
  aside,
  baseline,
}

enum DropCapPosition {
  start,
  end,
  bottomRight,
}

class DropCap extends StatelessWidget {
  final Widget child;
  final double width, height;

  const DropCap(
      {super.key,
      required this.child,
      required this.width,
      required this.height});

  @override
  Widget build(BuildContext context) =>
      SizedBox(width: width, height: height, child: child);
}

class DropCapText extends StatelessWidget {
  final String data;
  final DropCapMode mode;
  final TextStyle? style, dropCapStyle;
  final TextAlign textAlign;
  final DropCap? dropCap;
  final EdgeInsets dropCapPadding;
  final Offset indentation;
  final bool forceNoDescent, parseInlineMarkdown;
  final TextDirection textDirection;
  final DropCapPosition? dropCapPosition;
  final int dropCapChars;
  final int? maxLines;
  final TextOverflow overflow;

  const DropCapText(
    this.data, {
    super.key,
    this.mode = DropCapMode.inside,
    this.style,
    this.dropCapStyle,
    this.textAlign = TextAlign.start,
    this.dropCap,
    this.dropCapPadding = EdgeInsets.zero,
    this.indentation = Offset.zero,
    this.dropCapChars = 1,
    this.forceNoDescent = false,
    this.parseInlineMarkdown = false,
    this.textDirection = TextDirection.ltr,
    this.overflow = TextOverflow.clip,
    this.maxLines,
    this.dropCapPosition,
  });

  @override
  Widget build(BuildContext context) {
    final textStyle = const TextStyle(fontSize: 14, height: 1.3).merge(style);
    final capStyle = TextStyle(
      color: textStyle.color,
      fontSize: textStyle.fontSize! * 5.5,
      fontFamily: textStyle.fontFamily,
      fontWeight: textStyle.fontWeight,
      fontStyle: textStyle.fontStyle,
      height: 1,
    ).merge(dropCapStyle);

    final mdData = parseInlineMarkdown ? MarkdownParser(data) : null;
    final dropCapStr = (mdData?.plainText ?? data)
        .substring(0, dropCap != null ? 0 : dropCapChars);
    final mdRest = parseInlineMarkdown ? mdData!.subchars(dropCapChars) : null;
    final restData = data.substring(dropCap != null ? 0 : dropCapChars);

    double capWidth, capHeight;
    if (dropCap != null) {
      capWidth = dropCap!.width;
      capHeight = dropCap!.height;
    } else {
      final capPainter = TextPainter(
        text: TextSpan(text: dropCapStr, style: capStyle),
        textDirection: textDirection,
      )..layout();
      capWidth = capPainter.width;
      capHeight = capPainter.height;
      if (forceNoDescent) {
        final metrics = capPainter.computeLineMetrics();
        if (metrics.isNotEmpty) capHeight -= metrics[0].descent * 0.95;
      }
    }

    capWidth += dropCapPadding.horizontal;
    capHeight += dropCapPadding.vertical;

    return LayoutBuilder(builder: (context, constraints) {
      final restParagraphs = restData.split('\n');
      final children = <Widget>[];

      for (int p = 0; p < restParagraphs.length; p++) {
        final para = restParagraphs[p];
        final span = TextSpan(text: para, style: textStyle);
        final tp = TextPainter(
          text: span,
          textDirection: textDirection,
          textAlign: textAlign,
        )..layout(maxWidth: constraints.maxWidth);

        final lines = tp.computeLineMetrics();
        final lineHeight = tp.preferredLineHeight;

        if (dropCapPosition == DropCapPosition.bottomRight &&
            p == restParagraphs.length - 1) {
          final dropCapLines = (capHeight / lineHeight).ceil();
          final splitLine = max(0, lines.length - dropCapLines);

          int charSplit = 0;
          double yPos = 0;
          for (int i = 0; i < lines.length; i++) {
            if (i == splitLine) break;
            yPos += lines[i].height;
            final pos = tp.getPositionForOffset(
                Offset(tp.width - 1, yPos - lines[i].descent));
            charSplit = pos.offset;
          }

          final beforeText = para.substring(0, min(charSplit, para.length));
          final afterText = para.substring(min(charSplit, para.length));

          if (beforeText.isNotEmpty) {
            children.add(RichText(
              text: TextSpan(text: beforeText, style: textStyle),
              textDirection: textDirection,
              textAlign: textAlign,
            ));
          }

          children.add(Row(
            crossAxisAlignment: lines.length > 1
                ? CrossAxisAlignment.end
                : CrossAxisAlignment.start,
            children: [
              Expanded(
                child: RichText(
                  text: TextSpan(text: afterText, style: textStyle),
                  textDirection: textDirection,
                  textAlign: textAlign,
                ),
              ),
              Padding(
                padding: dropCapPadding,
                child: dropCap ??
                    RichText(
                      textDirection: textDirection,
                      textAlign: textAlign,
                      text: TextSpan(text: dropCapStr, style: capStyle),
                    ),
              ),
            ],
          ));
        } else {
          children.add(RichText(
            text: span,
            textDirection: textDirection,
            textAlign: textAlign,
          ));
        }

        if (p < restParagraphs.length - 1) {
          children.add(const SizedBox(height: 4));
        }
      }

      return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: children,
      );
    });
  }
}

// 以下 MarkdownParser 与 MarkdownSpan 与 Markup 类保持原样即可
class MarkdownParser {
  final String data;
  late List<MarkdownSpan> spans;
  String plainText = '';

  List<TextSpan> toTextSpanList() => spans.map((s) => s.toTextSpan()).toList();

  MarkdownParser subchars(int startIndex, [int? endIndex]) {
    final subspans = <MarkdownSpan>[];
    int skip = startIndex;
    for (var span in spans) {
      if (skip <= 0) {
        subspans.add(span);
      } else if (span.text.length < skip) {
        skip -= span.text.length;
      } else {
        subspans.add(MarkdownSpan(
          style: span.style,
          markups: span.markups,
          text: span.text.substring(skip),
        ));
        skip = 0;
      }
    }
    return MarkdownParser(subspans.map((e) => e.text).join());
  }

  MarkdownParser(this.data) {
    plainText = '';
    spans = [MarkdownSpan(text: '', markups: [], style: const TextStyle())];

    bool bold = false, italic = false, underline = false;
    const b = '**', i = '_', u = '++';

    void addSpan(String markup, bool isOpening) {
      final markups = [Markup(markup, isOpening)];
      if (bold && markup != b) markups.add(Markup(b, true));
      if (italic && markup != i) markups.add(Markup(i, true));
      if (underline && markup != u) markups.add(Markup(u, true));

      spans.add(MarkdownSpan(
        text: '',
        markups: markups,
        style: TextStyle(
          fontWeight: bold ? FontWeight.bold : null,
          fontStyle: italic ? FontStyle.italic : null,
          decoration: underline ? TextDecoration.underline : null,
        ),
      ));
    }

    bool check(int i, String m) =>
        data.substring(i, min(i + m.length, data.length)) == m;

    for (int c = 0; c < data.length; c++) {
      if (check(c, b)) {
        bold = !bold;
        addSpan(b, bold);
        c += b.length - 1;
      } else if (check(c, i)) {
        italic = !italic;
        addSpan(i, italic);
        c += i.length - 1;
      } else if (check(c, u)) {
        underline = !underline;
        addSpan(u, underline);
        c += u.length - 1;
      } else {
        spans.last.text += data[c];
        plainText += data[c];
      }
    }
  }
}

class MarkdownSpan {
  final TextStyle style;
  final List<Markup> markups;
  String text;

  MarkdownSpan(
      {required this.text, required this.style, required this.markups});

  TextSpan toTextSpan() => TextSpan(text: text, style: style);
}

class Markup {
  final String code;
  final bool isActive;

  Markup(this.code, this.isActive);
}
