import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:linkify/linkify.dart' as linkify_pkg;

const TextStyle kSearchHighlightStyle = TextStyle(
  backgroundColor: Color(0xFFFFD54F),
  color: Colors.black,
);

List<TextSpan> buildHighlightedSpans({
  required String text,
  required String? query,
  required TextStyle baseStyle,
  TextStyle highlightStyle = kSearchHighlightStyle,
  GestureRecognizer? recognizer,
}) {
  final q = query?.trim().toLowerCase();
  if (q == null || q.isEmpty) {
    return [TextSpan(text: text, style: baseStyle, recognizer: recognizer)];
  }

  final spans = <TextSpan>[];
  final lower = text.toLowerCase();
  var cursor = 0;
  while (cursor < text.length) {
    final hit = lower.indexOf(q, cursor);
    if (hit < 0) {
      spans.add(
        TextSpan(
          text: text.substring(cursor),
          style: baseStyle,
          recognizer: recognizer,
        ),
      );
      break;
    }
    if (hit > cursor) {
      spans.add(
        TextSpan(
          text: text.substring(cursor, hit),
          style: baseStyle,
          recognizer: recognizer,
        ),
      );
    }
    final end = hit + q.length;
    spans.add(
      TextSpan(
        text: text.substring(hit, end),
        style: baseStyle.merge(highlightStyle),
        recognizer: recognizer,
      ),
    );
    cursor = end;
  }
  return spans;
}

class HighlightedLinkify extends StatefulWidget {
  final String text;
  final String? highlight;
  final LinkCallback? onOpen;
  final TextStyle? style;
  final TextStyle? linkStyle;

  const HighlightedLinkify({
    required this.text,
    this.highlight,
    this.onOpen,
    this.style,
    this.linkStyle,
    super.key,
  });

  @override
  State<HighlightedLinkify> createState() => _HighlightedLinkifyState();
}

class _HighlightedLinkifyState extends State<HighlightedLinkify> {
  // Cached per link text — search rebuilds keystroke-by-keystroke
  // would otherwise churn allocate/dispose. Pruned via [_seenLinkKeys].
  final Map<String, TapGestureRecognizer> _recognizers = {};
  final Set<String> _seenLinkKeys = {};

  @override
  void dispose() {
    for (final r in _recognizers.values) {
      r.dispose();
    }
    _recognizers.clear();
    super.dispose();
  }

  TapGestureRecognizer _recognizerFor(LinkableElement el) {
    final key = el.text;
    final existing = _recognizers[key];
    if (existing != null) {
      // Refresh onTap so a parent rebuild's new closure is picked up.
      existing.onTap = () => widget.onOpen?.call(el);
      return existing;
    }
    final created = TapGestureRecognizer()
      ..onTap = () => widget.onOpen?.call(el);
    _recognizers[key] = created;
    return created;
  }

  void _pruneUnseen() {
    final stale = _recognizers.keys
        .where((k) => !_seenLinkKeys.contains(k))
        .toList(growable: false);
    for (final k in stale) {
      _recognizers.remove(k)?.dispose();
    }
  }

  @override
  Widget build(BuildContext context) {
    _seenLinkKeys.clear();

    final defaultStyle = widget.style ??
        Theme.of(context).textTheme.bodyMedium ??
        DefaultTextStyle.of(context).style;
    // Default first, link style on top — reversing the merge silently
    // drops link color/underline because TextStyle.merge treats explicit
    // nulls in the overlay as "leave unchanged".
    final linkStyle = defaultStyle.merge(
      widget.linkStyle ??
          const TextStyle(
            color: Colors.blue,
            decoration: TextDecoration.underline,
          ),
    );
    const linkHighlight = TextStyle(
      backgroundColor: Color(0xFFFFD54F),
      color: Colors.black,
      decoration: TextDecoration.underline,
    );

    final elements = linkify_pkg.linkify(widget.text);
    final spans = <InlineSpan>[];

    for (final el in elements) {
      if (el is LinkableElement) {
        _seenLinkKeys.add(el.text);
        final recognizer = _recognizerFor(el);
        spans.addAll(
          buildHighlightedSpans(
            text: el.text,
            query: widget.highlight,
            baseStyle: linkStyle,
            highlightStyle: linkHighlight,
            recognizer: recognizer,
          ),
        );
      } else {
        spans.addAll(
          buildHighlightedSpans(
            text: el.text,
            query: widget.highlight,
            baseStyle: defaultStyle,
          ),
        );
      }
    }

    _pruneUnseen();

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