/// Common JSON models for WAYZ location APIs.
///
/// These models are designed to be "MethodChannel friendly":
/// - use only plain Dart types (String/num/bool/null/List/Map)
/// - nested objects are represented as Dart classes with `fromJson/toJson`
/// - no custom serialization beyond JSON-compatible structures
library wayz_common_models;

class WzJson {
  static double? toDouble(dynamic v) {
    if (v == null) return null;
    if (v is num) return v.toDouble();
    final s = v.toString().trim();
    if (s.isEmpty) return null;
    return double.tryParse(s);
  }

  static int? toInt(dynamic v) {
    if (v == null) return null;
    if (v is int) return v;
    if (v is num) return v.toInt();
    final s = v.toString().trim();
    if (s.isEmpty) return null;
    return int.tryParse(s);
  }

  static bool? toBool(dynamic v) {
    if (v == null) return null;
    if (v is bool) return v;
    final s = v.toString().trim();
    if (s.isEmpty) return null;
    return bool.tryParse(s);
  }

  static String? toStringOrNull(dynamic v) {
    if (v == null) return null;
    if (v is String) return v;
    return v.toString();
  }

  static List<T>? toList<T>(
    dynamic v,
    T Function(dynamic e) fromJsonT,
  ) {
    if (v == null) return null;
    if (v is List) {
      return v.map(fromJsonT).toList();
    }
    return null;
  }
}

class WzLatLng {
  final double lat;
  final double lng;

  WzLatLng({required this.lat, required this.lng});

  factory WzLatLng.fromJson(Map<String, dynamic> json) {
    return WzLatLng(
      lat: WzJson.toDouble(json['lat']) ?? 0,
      lng: WzJson.toDouble(json['lng']) ?? 0,
    );
  }

  Map<String, dynamic> toJson() => {
        'lat': lat,
        'lng': lng,
      };
}

class WzPoint {
  final double longitude;
  final double latitude;
  final double? altitude;

  WzPoint({
    required this.longitude,
    required this.latitude,
    this.altitude,
  });

  factory WzPoint.fromJson(Map<String, dynamic> json) {
    // Most endpoints use "longitude/latitude", some use "lon/lat".
    final lon = json['longitude'] ?? json['lon'];
    final lat = json['latitude'] ?? json['lat'];
    return WzPoint(
      longitude: WzJson.toDouble(lon) ?? 0,
      latitude: WzJson.toDouble(lat) ?? 0,
      altitude: WzJson.toDouble(json['altitude']),
    );
  }

  Map<String, dynamic> toJson() {
    final out = <String, dynamic>{
      'longitude': longitude,
      'latitude': latitude,
    };
    if (altitude != null) out['altitude'] = altitude;
    return out;
  }
}

class WzPosition {
  /// Unix timestamp in milliseconds.
  final int? timestamp;
  final WzPoint point;
  final String? spatialReference;
  final String? source;
  final double? accuracy;
  final double? confidence;

  WzPosition({
    this.timestamp,
    required this.point,
    this.spatialReference,
    this.source,
    this.accuracy,
    this.confidence,
  });

  factory WzPosition.fromJson(Map<String, dynamic> json) {
    return WzPosition(
      timestamp: WzJson.toInt(json['timestamp']),
      point: WzPoint.fromJson(
        (json['point'] as Map?)?.cast<String, dynamic>() ?? const {},
      ),
      spatialReference: WzJson.toStringOrNull(json['spatialReference']),
      source: WzJson.toStringOrNull(json['source']),
      accuracy: WzJson.toDouble(json['accuracy']),
      confidence: WzJson.toDouble(json['confidence']),
    );
  }

  Map<String, dynamic> toJson() {
    final out = <String, dynamic>{
      'timestamp': timestamp,
      'point': point.toJson(),
    };
    if (spatialReference != null) out['spatialReference'] = spatialReference;
    if (source != null) out['source'] = source;
    if (accuracy != null) out['accuracy'] = accuracy;
    if (confidence != null) out['confidence'] = confidence;
    return out;
  }
}

class WzContext {
  final String? code;
  final String name;
  final String type;

  WzContext({
    this.code,
    required this.name,
    required this.type,
  });

  factory WzContext.fromJson(Map<String, dynamic> json) {
    return WzContext(
      code: WzJson.toStringOrNull(json['code']),
      name: WzJson.toStringOrNull(json['name']) ?? '',
      type: WzJson.toStringOrNull(json['type']) ?? '',
    );
  }

  Map<String, dynamic> toJson() {
    final out = <String, dynamic>{
      'name': name,
      'type': type,
    };
    if (code != null) out['code'] = code;
    return out;
  }
}

class WzAddress {
  final String name;
  final int? level;
  final List<WzContext> context;

  WzAddress({
    required this.name,
    this.level,
    required this.context,
  });

  factory WzAddress.fromJson(Map<String, dynamic> json) {
    return WzAddress(
      name: WzJson.toStringOrNull(json['name']) ?? '',
      level: WzJson.toInt(json['level']),
      context: WzJson.toList<WzContext>(
                json['context'],
                (e) => WzContext.fromJson(
                  (e as Map).cast<String, dynamic>(),
                ),
              ) ??
          const [],
    );
  }

  Map<String, dynamic> toJson() {
    final out = <String, dynamic>{
      'name': name,
      'context': context.map((e) => e.toJson()).toList(),
    };
    if (level != null) out['level'] = level;
    return out;
  }
}

class WzDistance {
  final double line;

  WzDistance({required this.line});

  factory WzDistance.fromJson(Map<String, dynamic> json) {
    return WzDistance(line: WzJson.toDouble(json['line']) ?? 0);
  }

  Map<String, dynamic> toJson() => {'line': line};
}

class WzCategory {
  final dynamic id; // Number or String depending on endpoint/version
  final String name;

  WzCategory({this.id, required this.name});

  factory WzCategory.fromJson(Map<String, dynamic> json) {
    return WzCategory(
      id: json['id'],
      name: WzJson.toStringOrNull(json['name']) ?? '',
    );
  }

  Map<String, dynamic> toJson() => {
        'id': id,
        'name': name,
      };
}

class WzPlace {
  final String? id;
  final String? name;
  final String? type;
  final WzDistance? distance;
  final List<WzCategory>? categories;

  WzPlace({
    this.id,
    this.name,
    this.type,
    this.distance,
    this.categories,
  });

  factory WzPlace.fromJson(Map<String, dynamic> json) {
    final distanceJson = json['distance'];
    return WzPlace(
      id: WzJson.toStringOrNull(json['id']),
      name: WzJson.toStringOrNull(json['name']),
      type: WzJson.toStringOrNull(json['type']),
      distance: distanceJson is Map<String, dynamic>
          ? WzDistance.fromJson(distanceJson)
          : (distanceJson is Map)
              ? WzDistance.fromJson(distanceJson.cast<String, dynamic>())
              : null,
      categories: WzJson.toList<WzCategory>(
        json['categories'],
        (e) => WzCategory.fromJson((e as Map).cast<String, dynamic>()),
      ),
    );
  }

  Map<String, dynamic> toJson() {
    final out = <String, dynamic>{};
    if (id != null) out['id'] = id;
    if (name != null) out['name'] = name;
    if (type != null) out['type'] = type;
    if (distance != null) out['distance'] = distance!.toJson();
    if (categories != null) out['categories'] = categories!.map((e) => e.toJson()).toList();
    return out;
  }
}

class WzLocation {
  final int? timestamp;
  final WzAddress? address;
  final WzPlace? place;
  final WzPosition? position;

  WzLocation({
    this.timestamp,
    this.address,
    this.place,
    this.position,
  });

  factory WzLocation.fromJson(Map<String, dynamic> json) {
    return WzLocation(
      timestamp: WzJson.toInt(json['timestamp']),
      address: json['address'] is Map
          ? WzAddress.fromJson((json['address'] as Map).cast<String, dynamic>())
          : null,
      place: json['place'] is Map
          ? WzPlace.fromJson((json['place'] as Map).cast<String, dynamic>())
          : null,
      position: json['position'] is Map
          ? WzPosition.fromJson((json['position'] as Map).cast<String, dynamic>())
          : null,
    );
  }

  Map<String, dynamic> toJson() {
    final out = <String, dynamic>{};
    if (timestamp != null) out['timestamp'] = timestamp;
    if (address != null) out['address'] = address!.toJson();
    if (place != null) out['place'] = place!.toJson();
    if (position != null) out['position'] = position!.toJson();
    return out;
  }
}

class WzGeoJson {
  final String type;
  final dynamic coordinates;

  WzGeoJson({required this.type, this.coordinates});

  factory WzGeoJson.fromJson(Map<String, dynamic> json) {
    return WzGeoJson(
      type: WzJson.toStringOrNull(json['type']) ?? '',
      coordinates: json['coordinates'],
    );
  }

  Map<String, dynamic> toJson() => {
        'type': type,
        if (coordinates != null) 'coordinates': coordinates,
      };
}

class WzApiError {
  final String? code;
  final String message;

  WzApiError({this.code, required this.message});

  factory WzApiError.fromJson(Map<String, dynamic> json) {
    return WzApiError(
      code: WzJson.toStringOrNull(json['code']),
      message: WzJson.toStringOrNull(json['message']) ?? '',
    );
  }

  Map<String, dynamic> toJson() {
    final out = <String, dynamic>{'message': message};
    if (code != null) out['code'] = code;
    return out;
  }
}

