""" Maps domain serializers for ThrillWiki API v1. This module contains all serializers related to map functionality, including location data, search results, and clustering. """ from rest_framework import serializers from drf_spectacular.utils import ( extend_schema_serializer, extend_schema_field, OpenApiExample, ) # === MAP LOCATION SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Map Location Example", summary="Example map location response", description="A location point on the map", value={ "id": 1, "type": "park", "name": "Cedar Point", "slug": "cedar-point", "latitude": 41.4793, "longitude": -82.6833, "status": "OPERATING", "location": { "city": "Sandusky", "state": "Ohio", "country": "United States", }, "stats": { "coaster_count": 17, "ride_count": 70, "average_rating": 4.5, }, }, ) ] ) class MapLocationSerializer(serializers.Serializer): """Serializer for individual map locations (parks and rides).""" id = serializers.IntegerField() type = serializers.CharField() # 'park' or 'ride' name = serializers.CharField() slug = serializers.CharField() latitude = serializers.FloatField(allow_null=True) longitude = serializers.FloatField(allow_null=True) status = serializers.CharField() # Location details location = serializers.SerializerMethodField() # Statistics stats = serializers.SerializerMethodField() @extend_schema_field(serializers.DictField()) def get_location(self, obj) -> dict: """Get location information.""" if hasattr(obj, "location") and obj.location: return { "city": obj.location.city, "state": obj.location.state, "country": obj.location.country, "formatted_address": obj.location.formatted_address, } return {} @extend_schema_field(serializers.DictField()) def get_stats(self, obj) -> dict: """Get relevant statistics based on object type.""" if obj._meta.model_name == "park": return { "coaster_count": obj.coaster_count or 0, "ride_count": obj.ride_count or 0, "average_rating": ( float(obj.average_rating) if obj.average_rating else None ), } elif obj._meta.model_name == "ride": return { "category": obj.get_category_display() if obj.category else None, "average_rating": ( float(obj.average_rating) if obj.average_rating else None ), "park_name": obj.park.name if obj.park else None, } return {} @extend_schema_serializer( examples=[ OpenApiExample( "Map Cluster Example", summary="Example map cluster response", description="A cluster of locations on the map", value={ "id": "cluster_1", "type": "cluster", "latitude": 41.5, "longitude": -82.7, "count": 5, "bounds": { "north": 41.6, "south": 41.4, "east": -82.6, "west": -82.8, }, }, ) ] ) class MapClusterSerializer(serializers.Serializer): """Serializer for map clusters.""" id = serializers.CharField() type = serializers.CharField(default="cluster") latitude = serializers.FloatField() longitude = serializers.FloatField() count = serializers.IntegerField() bounds = serializers.DictField() @extend_schema_serializer( examples=[ OpenApiExample( "Map Locations Response Example", summary="Example map locations response", description="Response containing locations and optional clusters", value={ "status": "success", "data": { "locations": [ { "id": 1, "type": "park", "name": "Cedar Point", "slug": "cedar-point", "latitude": 41.4793, "longitude": -82.6833, "status": "OPERATING", } ], "clusters": [], "bounds": { "north": 41.5, "south": 41.4, "east": -82.6, "west": -82.8, }, "total_count": 1, "clustered": False, }, }, ) ] ) class MapLocationsResponseSerializer(serializers.Serializer): """Response serializer for map locations endpoint.""" status = serializers.CharField(default="success") locations = serializers.ListField(child=serializers.DictField()) clusters = serializers.ListField(child=serializers.DictField(), default=list) bounds = serializers.DictField(default=dict) total_count = serializers.IntegerField(default=0) clustered = serializers.BooleanField(default=False) # === MAP SEARCH SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Map Search Result Example", summary="Example map search result", description="A search result for map locations", value={ "id": 1, "type": "park", "name": "Cedar Point", "slug": "cedar-point", "latitude": 41.4793, "longitude": -82.6833, "location": { "city": "Sandusky", "state": "Ohio", "country": "United States", }, "relevance_score": 0.95, }, ) ] ) class MapSearchResultSerializer(serializers.Serializer): """Serializer for map search results.""" id = serializers.IntegerField() type = serializers.CharField() name = serializers.CharField() slug = serializers.CharField() latitude = serializers.FloatField(allow_null=True) longitude = serializers.FloatField(allow_null=True) location = serializers.SerializerMethodField() relevance_score = serializers.FloatField(required=False) @extend_schema_field(serializers.DictField()) def get_location(self, obj) -> dict: """Get location information.""" if hasattr(obj, "location") and obj.location: return { "city": obj.location.city, "state": obj.location.state, "country": obj.location.country, } return {} @extend_schema_serializer( examples=[ OpenApiExample( "Map Search Response Example", summary="Example map search response", description="Response containing search results", value={ "status": "success", "data": { "results": [ { "id": 1, "type": "park", "name": "Cedar Point", "slug": "cedar-point", "latitude": 41.4793, "longitude": -82.6833, } ], "query": "cedar point", "total_count": 1, "page": 1, "page_size": 20, }, }, ) ] ) class MapSearchResponseSerializer(serializers.Serializer): """Response serializer for map search endpoint.""" status = serializers.CharField(default="success") results = serializers.ListField(child=serializers.DictField()) query = serializers.CharField() total_count = serializers.IntegerField(default=0) page = serializers.IntegerField(default=1) page_size = serializers.IntegerField(default=20) # === MAP DETAIL SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Map Location Detail Example", summary="Example map location detail response", description="Detailed information about a specific location", value={ "id": 1, "type": "park", "name": "Cedar Point", "slug": "cedar-point", "description": "America's Roller Coast", "latitude": 41.4793, "longitude": -82.6833, "status": "OPERATING", "location": { "street_address": "1 Cedar Point Dr", "city": "Sandusky", "state": "Ohio", "country": "United States", "postal_code": "44870", "formatted_address": "1 Cedar Point Dr, Sandusky, Ohio, 44870, United States", }, "stats": { "coaster_count": 17, "ride_count": 70, "average_rating": 4.5, }, "nearby_locations": [], }, ) ] ) class MapLocationDetailSerializer(serializers.Serializer): """Serializer for detailed map location information.""" id = serializers.IntegerField() type = serializers.CharField() name = serializers.CharField() slug = serializers.CharField() description = serializers.CharField() latitude = serializers.FloatField(allow_null=True) longitude = serializers.FloatField(allow_null=True) status = serializers.CharField() # Detailed location information location = serializers.SerializerMethodField() # Statistics stats = serializers.SerializerMethodField() # Nearby locations nearby_locations = serializers.SerializerMethodField() @extend_schema_field(serializers.DictField()) def get_location(self, obj) -> dict: """Get detailed location information.""" if hasattr(obj, "location") and obj.location: return { "street_address": obj.location.street_address, "city": obj.location.city, "state": obj.location.state, "country": obj.location.country, "postal_code": obj.location.postal_code, "formatted_address": obj.location.formatted_address, } return {} @extend_schema_field(serializers.DictField()) def get_stats(self, obj) -> dict: """Get detailed statistics based on object type.""" if obj._meta.model_name == "park": return { "coaster_count": obj.coaster_count or 0, "ride_count": obj.ride_count or 0, "average_rating": ( float(obj.average_rating) if obj.average_rating else None ), "size_acres": float(obj.size_acres) if obj.size_acres else None, "opening_date": ( obj.opening_date.isoformat() if obj.opening_date else None ), } elif obj._meta.model_name == "ride": return { "category": obj.get_category_display() if obj.category else None, "average_rating": ( float(obj.average_rating) if obj.average_rating else None ), "park_name": obj.park.name if obj.park else None, "opening_date": ( obj.opening_date.isoformat() if obj.opening_date else None ), "manufacturer": obj.manufacturer.name if obj.manufacturer else None, } return {} @extend_schema_field(serializers.ListField(child=serializers.DictField())) def get_nearby_locations(self, obj) -> list: """Get nearby locations (placeholder for now).""" # TODO: Implement nearby location logic return [] # === INPUT SERIALIZERS === class MapBoundsInputSerializer(serializers.Serializer): """Input serializer for map bounds queries.""" north = serializers.FloatField(min_value=-90, max_value=90) south = serializers.FloatField(min_value=-90, max_value=90) east = serializers.FloatField(min_value=-180, max_value=180) west = serializers.FloatField(min_value=-180, max_value=180) def validate(self, attrs): """Validate that bounds make geographic sense.""" if attrs["north"] <= attrs["south"]: raise serializers.ValidationError( "North bound must be greater than south bound" ) # Handle longitude wraparound (e.g., crossing the international date line) # For now, we'll require west < east for simplicity if attrs["west"] >= attrs["east"]: raise serializers.ValidationError("West bound must be less than east bound") return attrs class MapSearchInputSerializer(serializers.Serializer): """Input serializer for map search queries.""" q = serializers.CharField(min_length=1, max_length=255) types = serializers.CharField(required=False, allow_blank=True) bounds = MapBoundsInputSerializer(required=False) page = serializers.IntegerField(min_value=1, default=1) page_size = serializers.IntegerField(min_value=1, max_value=100, default=20) def validate_types(self, value): """Validate location types.""" if not value: return [] valid_types = ["park", "ride"] types = [t.strip().lower() for t in value.split(",")] for location_type in types: if location_type not in valid_types: raise serializers.ValidationError( f"Invalid location type: {location_type}. Valid types: {', '.join(valid_types)}" ) return types