import 'package:aves/model/gpx/gpx_track.dart';
import 'package:latlong2/latlong.dart';
import 'package:xml/xml.dart';

class GpxParser {
  static GpxTrack parse(
    String xmlContent, {
    String fallbackName = 'GPX track',
  }) {
    final document = XmlDocument.parse(xmlContent);

    final name = _readTrackName(document) ?? fallbackName;
    final segments = <List<LatLng>>[];

    // Standard GPX tracks: trk/trkseg/trkpt
    for (final segment in _elementsByLocalName(document, 'trkseg')) {
      final points = _readLatLngElements(
        segment.findElements('trkpt'),
      );

      if (points.length >= 2) {
        segments.add(points);
      }
    }

    // GPX routes: rte/rtept
    for (final route in _elementsByLocalName(document, 'rte')) {
      final points = _readLatLngElements(
        route.findElements('rtept'),
      );

      if (points.length >= 2) {
        segments.add(points);
      }
    }

    // Fallback: waypoints as a single line only if there are enough.
    if (segments.isEmpty) {
      final waypoints = _readLatLngElements(
        _elementsByLocalName(document, 'wpt'),
      );

      if (waypoints.length >= 2) {
        segments.add(waypoints);
      }
    }

    return GpxTrack(
      name: name,
      tracks: segments.toSet(),
    );
  }

  static List<LatLng> _readLatLngElements(
    Iterable<XmlElement> elements,
  ) {
    final points = <LatLng>[];

    for (final point in elements) {
      final latString = point.getAttribute('lat');
      final lonString = point.getAttribute('lon');

      if (latString == null || lonString == null) continue;

      final lat = double.tryParse(latString);
      final lon = double.tryParse(lonString);

      if (lat == null || lon == null) continue;
      if (!lat.isFinite || !lon.isFinite) continue;

      points.add(LatLng(lat, lon));
    }

    return points;
  }

  static String? _readTrackName(XmlDocument document) {
    for (final track in _elementsByLocalName(document, 'trk')) {
      for (final name in track.findElements('name')) {
        final text = name.innerText.trim();
        if (text.isNotEmpty) return text;
      }
    }

    for (final route in _elementsByLocalName(document, 'rte')) {
      for (final name in route.findElements('name')) {
        final text = name.innerText.trim();
        if (text.isNotEmpty) return text;
      }
    }

    for (final name in _elementsByLocalName(document, 'name')) {
      final text = name.innerText.trim();
      if (text.isNotEmpty) return text;
    }

    return null;
  }

  static Iterable<XmlElement> _elementsByLocalName(
    XmlNode node,
    String localName,
  ) {
    return node.descendants.whereType<XmlElement>().where(
          (element) => element.name.local == localName,
        );
  }
}
