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

import '../services/link_trust_service.dart';
import '../services/ts3_server_link.dart';

final _tagRe = RegExp(
  r'\[(\/?(?:b|i|u|s'
  r'|color(?:=[^\]]*)?'
  r'|size(?:=\d+)?'
  r'|url(?:=[^\]]*)?'
  r'|img(?:=[^\]]*)?'
  r'|list|\*|quote|code|center|left|right'
  r'))\]',
  caseSensitive: false,
);
final _urlRe = RegExp(r'url=(.+)', caseSensitive: false);
final _imgRe = RegExp(r'img=(.+)');
final _urlAutoRe = RegExp(
  r'(?:\[url\])?((?:https?|ts3server)://[^\s\[\]]+)(?:\[/url\])?',
  caseSensitive: false,
);
final _closeUrlRe = RegExp(r'\[/url\]', caseSensitive: false);
const _linkStyle = TextStyle(color: Colors.blue);

int? _findCloseUrl(String src, int from) {
  final m = _closeUrlRe.matchAsPrefix(src, from);
  if (m != null) return from;
  final idx = src.indexOf('[/url]', from);
  if (idx >= 0) return idx;
  final idxu = src.indexOf('[/URL]', from);
  if (idxu >= 0) return idxu;
  return null;
}

Color? _parseColor(String hex) {
  try {
    var h = hex.replaceFirst('#', '');
    if (h.length == 6) h = 'FF$h';
    if (h.length == 8) {
      return Color(int.parse(h, radix: 16));
    }
  } catch (_) {}
  return null;
}

class BbCodeText extends StatelessWidget {
  const BbCodeText(
    this.text, {
    super.key,
    required this.linkTrust,
    this.onTs3ServerLink,
  });

  final String text;
  final LinkTrustService linkTrust;
  final Ts3ServerLinkHandler? onTs3ServerLink;

  @override
  Widget build(BuildContext context) {
    if (!text.contains('[') || !text.contains(']')) {
      return _plainWithAutoLinks(context, text);
    }
    return _render(context, text);
  }

  Widget _plainWithAutoLinks(BuildContext context, String src) {
    final parts = <InlineSpan>[];
    int last = 0;
    for (final m in _urlAutoRe.allMatches(src)) {
      if (m.start > last) {
        parts.add(TextSpan(text: src.substring(last, m.start)));
      }
      final url = m.group(1)!;
      parts.add(
        WidgetSpan(
          alignment: PlaceholderAlignment.middle,
          child: _LinkTap(
            url: url,
            linkTrust: linkTrust,
            onTs3ServerLink: onTs3ServerLink,
            child: Text(url, style: _linkStyle),
          ),
        ),
      );
      last = m.end;
    }
    if (last < src.length) {
      parts.add(TextSpan(text: src.substring(last)));
    }
    if (parts.isEmpty) return Text(src);
    return Text.rich(TextSpan(children: parts));
  }

  Widget _render(BuildContext context, String src) {
    final spans = <InlineSpan>[];
    final tags = <String>[];

    void flush(StringBuffer buf) {
      if (buf.isEmpty) return;
      var t = buf.toString();
      buf.clear();

      bool bold = false;
      bool italic = false;
      bool underline = false;
      bool strikethrough = false;
      Color? color;
      double? size;

      for (final tag in tags) {
        if (tag == 'b') {
          bold = true;
        } else if (tag == 'i') {
          italic = true;
        } else if (tag == 'u') {
          underline = true;
        } else if (tag == 's') {
          strikethrough = true;
        } else if (tag.startsWith('color=')) {
          color = _parseColor(tag.substring(6));
        } else if (tag.startsWith('size=')) {
          size = double.tryParse(tag.substring(5));
        }
      }

      spans.add(
        TextSpan(
          text: t,
          style: TextStyle(
            fontWeight: bold ? FontWeight.bold : null,
            fontStyle: italic ? FontStyle.italic : null,
            decoration: TextDecoration.combine([
              if (underline) TextDecoration.underline,
              if (strikethrough) TextDecoration.lineThrough,
            ]),
            color: color,
            fontSize: size,
          ),
        ),
      );
    }

    final buf = StringBuffer();
    int i = 0;

    while (i < src.length) {
      if (src[i] != '[') {
        buf.write(src[i]);
        i++;
        continue;
      }
      final m = _tagRe.matchAsPrefix(src, i);
      if (m == null) {
        buf.write(src[i]);
        i++;
        continue;
      }

      flush(buf);
      final rawTag = m.group(1)!;
      final raw = rawTag.toLowerCase();
      i = m.end;

      if (raw.startsWith('/')) {
        final closeTag = raw.substring(1);
        if (closeTag == 'url' || closeTag == 'img') {
          continue;
        }
        tags.remove(closeTag);
        continue;
      }

      switch (raw) {
        case 'b':
        case 'i':
        case 'u':
        case 's':
        case 'list':
        case 'quote':
        case 'code':
        case 'center':
        case 'left':
        case 'right':
          tags.add(raw);
          break;

        case '*':
          spans.add(const TextSpan(text: '\n \u2022 '));
          break;

        default:
          if (raw.startsWith('color=') || raw.startsWith('size=')) {
            tags.add(raw);
          } else if (raw.startsWith('url=')) {
            final url = _urlRe.firstMatch(rawTag)?.group(1) ?? '';
            final closeIdx = _findCloseUrl(src, i);
            String inner;
            if (closeIdx != null) {
              inner = src.substring(i, closeIdx);
              i = closeIdx + 6;
            } else {
              inner = url;
            }
            spans.add(
              WidgetSpan(
                alignment: PlaceholderAlignment.middle,
                child: _LinkTap(
                  url: url.isNotEmpty ? url : inner,
                  linkTrust: linkTrust,
                  onTs3ServerLink: onTs3ServerLink,
                  child: Text(inner, style: _linkStyle),
                ),
              ),
            );
          } else if (raw.startsWith('img=')) {
            final src2 = _imgRe.firstMatch(raw)?.group(1) ?? '';
            if (src2.isNotEmpty) {
              spans.add(
                WidgetSpan(
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(8),
                    child: Image.network(
                      Uri.tryParse(src2)?.toString() ?? src2,
                      fit: BoxFit.scaleDown,
                      errorBuilder: (_, _, _) => const SizedBox.shrink(),
                    ),
                  ),
                ),
              );
            }
          } else if (raw == 'url') {
            final closeIdx = _findCloseUrl(src, i);
            if (closeIdx != null) {
              final url = src.substring(i, closeIdx).trim();
              i = closeIdx + 6;
              spans.add(
                WidgetSpan(
                  alignment: PlaceholderAlignment.middle,
                  child: _LinkTap(
                    url: url,
                    linkTrust: linkTrust,
                    onTs3ServerLink: onTs3ServerLink,
                    child: Text(url, style: _linkStyle),
                  ),
                ),
              );
            }
          }
          break;
      }
    }

    flush(buf);

    if (spans.isEmpty) return Text(src);
    return Text.rich(TextSpan(children: spans));
  }
}

class _LinkTap extends StatefulWidget {
  const _LinkTap({
    required this.url,
    required this.child,
    required this.linkTrust,
    this.onTs3ServerLink,
  });

  final String url;
  final Widget child;
  final LinkTrustService linkTrust;
  final Ts3ServerLinkHandler? onTs3ServerLink;

  @override
  State<_LinkTap> createState() => _LinkTapState();
}

class _LinkTapState extends State<_LinkTap> {
  @override
  void initState() {
    super.initState();
    widget.linkTrust.addListener(_onChanged);
  }

  @override
  void dispose() {
    widget.linkTrust.removeListener(_onChanged);
    super.dispose();
  }

  void _onChanged() => mounted ? setState(() {}) : null;

  Future<void> _open() async {
    final ts3Link = parseTs3ServerLink(widget.url);
    if (ts3Link != null) {
      await widget.onTs3ServerLink?.call(ts3Link);
      return;
    }

    final uri = Uri.tryParse(widget.url);
    if (uri == null) return;
    final host = uri.host;
    if (host.isEmpty) return;

    if (widget.linkTrust.isTrusted(host)) {
      await launchUrl(uri, mode: LaunchMode.externalApplication);
      return;
    }

    if (!mounted) return;
    final trust = await showLinkTrustDialog(context, host);
    if (trust == null) return;
    if (trust) {
      await widget.linkTrust.addTrustedDomain(host);
    }
    await launchUrl(uri, mode: LaunchMode.externalApplication);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(onTap: _open, child: widget.child);
  }
}
