// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'package:flutter/widgets.dart';

class RichTextStyleParameter {
  const RichTextStyleParameter(
    this.defaultStyle,
    this.definedStyle,
    this.styleName,
  );

  final TextStyle? defaultStyle;
  final Map<String, TextStyle> definedStyle;
  final String? styleName;

  TextStyle get style {
    if (definedStyle.containsKey(styleName)) {
      return definedStyle[styleName]!;
    }
    return defaultStyle ?? const TextStyle();
  }

  RichTextStyleParameter copyWith({
    TextStyle? defaultStyle,
    Map<String, TextStyle>? definedStyle,
    String? styleName,
  }) =>
      RichTextStyleParameter(
        defaultStyle ?? this.defaultStyle,
        definedStyle ?? this.definedStyle,
        styleName ?? this.styleName,
      );
}

class RichTextNode {
  RichTextNode(this.nodes);

  final List<RichTextNode> nodes;

  static RichTextNode from(
    String content,
    RegExp regex,
    RichTextStyleParameter styleParameter,
  ) {
    final matches = regex.allMatches(content);
    if (matches.isNotEmpty) {
      // match found -> construct node with leaf/nodes
      final List<RichTextNode> nodes = [];
      for (var i = 0; i < matches.length; i++) {
        final previousMatch = i > 0 ? matches.elementAt(i - 1) : null;
        final currentMatch = matches.elementAt(i);
        // non match before
        final nonMatchBefore = (previousMatch != null)
            ? content.substring(previousMatch.end, currentMatch.start)
            : content.substring(0, currentMatch.start);
        nodes
          ..add(RichTextNode.from(nonMatchBefore, regex, styleParameter))
          // match
          ..add(
            RichTextNode.from(
              currentMatch.group(2)!,
              regex,
              styleParameter.copyWith(styleName: currentMatch.group(1)),
            ),
          );
      }
      // non match after
      final nonMatchAfter = content.substring(matches.last.end);
      nodes.add(RichTextNode.from(nonMatchAfter, regex, styleParameter));
      return RichTextNode(nodes);
    } else {
      // match not found -> construct leaf
      return RichTextLeaf(styleParameter.style, content);
    }
  }

  InlineSpan toInlineSpan(RichTextParser parser) {
    final children = <InlineSpan>[];
    for (final node in nodes) {
      children.add(node.toInlineSpan(parser));
    }
    return TextSpan(children: children);
  }
}

class RichTextLeaf extends RichTextNode {
  RichTextLeaf(this.style, this.content) : super([]);

  final TextStyle style;
  final String content;

  @override
  InlineSpan toInlineSpan(RichTextParser parser) =>
      parser.nodeBuilder.call(content, style);
}

class RichTextParser {
  const RichTextParser({required this.nodeBuilder});
  factory RichTextParser.defaultBuilder() => RichTextParser(
        nodeBuilder: (content, style) => TextSpan(
          text: content,
          style: style,
        ),
      );
  final InlineSpan Function(String content, TextStyle style) nodeBuilder;
}
