# ThrillWiki Frontend Integration Guide

**Last Updated:** 2025-01-15

**IMPORTANT:** All tuple-based choice patterns have been migrated to Rich Choice Objects system as of 2025-01-15.

This document provides comprehensive information for frontend developers integrating with the ThrillWiki Django backend API. It covers all endpoints, data structures, authentication, and integration patterns.

## Table of Contents

1. [API Overview](#api-overview)
2. [Authentication](#authentication)
3. [Rich Choice Objects](#rich-choice-objects)
4. [Core Endpoints](#core-endpoints)
5. [Data Models](#data-models)
6. [Error Handling](#error-handling)
7. [Integration Examples](#integration-examples)

## API Overview

The ThrillWiki API is built using Django REST Framework with comprehensive OpenAPI documentation. All endpoints follow RESTful conventions and return JSON responses.

**Base URL:** `https://api.thrillwiki.com/api/v1/`
**Documentation:** `https://api.thrillwiki.com/api/schema/swagger-ui/`

### Key Features

- **Rich Choice Objects**: Enhanced choice fields with metadata, colors, icons, and descriptions
- **Comprehensive Filtering**: Advanced filtering and search capabilities
- **Real-time Updates**: WebSocket support for live data updates
- **Media Integration**: Cloudflare Images integration for optimized photo delivery
- **Geographic Data**: PostGIS integration for location-based features

## Authentication

The API uses token-based authentication with support for both session and JWT tokens.

### Authentication Headers

```javascript
// For API requests
headers: {
  'Authorization': 'Token your-api-token-here',
  'Content-Type': 'application/json'
}

// For JWT (if using)
headers: {
  'Authorization': 'Bearer your-jwt-token-here',
  'Content-Type': 'application/json'
}
```

### Login Endpoint

```javascript
POST /api/v1/auth/login/
{
  "username": "your-username",
  "password": "your-password"
}

// Response
{
  "key": "your-api-token",
  "user": {
    "user_id": "1234",
    "username": "thrillseeker",
    "email": "user@example.com",
    "role": "USER",
    "theme_preference": "dark"
  }
}
```

## Rich Choice Objects

**CRITICAL:** ThrillWiki uses a Rich Choice Objects system instead of simple string choices. All choice fields return enhanced objects with metadata.

### Choice Field Structure

```typescript
interface RichChoice {
  value: string;           // The actual value stored in database
  label: string;           // Human-readable label
  description: string;     // Detailed description
  deprecated: boolean;     // Whether this choice is deprecated
  sort_order: number;      // Display order
  category: string;        // Choice category
  metadata: {
    color: string;         // UI color (e.g., "blue", "red")
    icon: string;          // Icon name (e.g., "user", "shield-check")
    css_class: string;     // CSS classes for styling
    [key: string]: any;    // Additional metadata
  };
}
```

### Using Rich Choices in Frontend

```javascript
// When displaying choice values, use the rich choice data
const userRole = user.get_role_rich_choice();
console.log(userRole.label);        // "Administrator"
console.log(userRole.metadata.color); // "purple"
console.log(userRole.metadata.icon);  // "cog"

// For forms, you can get all available choices
const roleChoices = await fetch('/api/v1/choices/accounts/user_roles/');
// Returns array of RichChoice objects
```

### Available Choice Groups

#### Accounts Domain
- `user_roles`: USER, MODERATOR, ADMIN, SUPERUSER
- `theme_preferences`: light, dark
- `privacy_levels`: public, friends, private
- `top_list_categories`: RC, DR, FR, WR, PK
- `notification_types`: submission_approved, review_helpful, etc.
- `notification_priorities`: low, normal, high, urgent

#### Parks Domain
- `statuses`: OPERATING, CLOSED_TEMP, CLOSED_PERM, etc.
- `types`: THEME_PARK, AMUSEMENT_PARK, WATER_PARK, etc.
- `company_roles`: OPERATOR, PROPERTY_OWNER

#### Rides Domain
- `statuses`: OPERATING, CLOSED_TEMP, SBNO, etc.
- `categories`: RC, DR, FR, WR, TR, OT
- `company_roles`: MANUFACTURER, DESIGNER
- `track_materials`: STEEL, WOOD, HYBRID

## Core Endpoints

### Parks API

#### List Parks
```javascript
GET /api/v1/parks/
GET /api/v1/parks/?search=cedar&status=OPERATING&park_type=THEME_PARK

// Response
{
  "count": 150,
  "next": "https://api.thrillwiki.com/api/v1/parks/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "Cedar Point",
      "slug": "cedar-point",
      "description": "Roller coaster capital of the world",
      "park_type": {
        "value": "THEME_PARK",
        "label": "Theme Park",
        "metadata": {
          "color": "blue",
          "icon": "castle"
        }
      },
      "status": {
        "value": "OPERATING",
        "label": "Operating",
        "metadata": {
          "color": "green",
          "icon": "check-circle"
        }
      },
      "location": {
        "city": "Sandusky",
        "state": "OH",
        "country": "USA",
        "coordinates": {
          "latitude": 41.4814,
          "longitude": -82.6838
        }
      },
      "operator": {
        "name": "Cedar Fair",
        "slug": "cedar-fair"
      },
      "stats": {
        "ride_count": 72,
        "coaster_count": 17,
        "average_rating": 8.7
      }
    }
  ]
}
```

#### Park Detail
```javascript
GET /api/v1/parks/cedar-point/

// Response includes full park details, rides, areas, photos, reviews
{
  "id": 1,
  "name": "Cedar Point",
  "slug": "cedar-point",
  "description": "America's Roller Coast...",
  "park_type": { /* Rich Choice Object */ },
  "status": { /* Rich Choice Object */ },
  "opening_date": "1870-05-30",
  "size_acres": "364.00",
  "location": { /* Full location data */ },
  "operator": { /* Company details */ },
  "areas": [
    {
      "id": 1,
      "name": "Main Midway",
      "description": "The heart of Cedar Point"
    }
  ],
  "rides": [
    {
      "id": 1,
      "name": "Steel Vengeance",
      "category": { /* Rich Choice Object */ },
      "status": { /* Rich Choice Object */ }
    }
  ],
  "photos": [
    {
      "id": 1,
      "photo_type": "banner",
      "image_url": "https://imagedelivery.net/...",
      "variants": {
        "thumbnail": "https://imagedelivery.net/.../thumbnail",
        "medium": "https://imagedelivery.net/.../medium",
        "large": "https://imagedelivery.net/.../large"
      }
    }
  ],
  "recent_reviews": [ /* Latest reviews */ ],
  "stats": {
    "total_reviews": 1247,
    "average_rating": 8.7,
    "ride_count": 72,
    "coaster_count": 17
  }
}
```

### Rides API

#### List Rides
```javascript
GET /api/v1/rides/
GET /api/v1/rides/?category=RC&status=OPERATING&manufacturer=bolliger-mabillard

// Response
{
  "count": 500,
  "results": [
    {
      "id": 1,
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "park": {
        "name": "Cedar Point",
        "slug": "cedar-point"
      },
      "category": {
        "value": "RC",
        "label": "Roller Coaster",
        "metadata": {
          "color": "red",
          "icon": "roller-coaster"
        }
      },
      "status": {
        "value": "OPERATING",
        "label": "Operating",
        "metadata": {
          "color": "green",
          "icon": "check-circle"
        }
      },
      "manufacturer": {
        "name": "Rocky Mountain Construction",
        "slug": "rocky-mountain-construction"
      },
      "stats": {
        "height_ft": "205.00",
        "speed_mph": "74.00",
        "length_ft": "5740.00"
      }
    }
  ]
}
```

#### Ride Detail
```javascript
GET /api/v1/rides/steel-vengeance/

// Response includes comprehensive ride data
{
  "id": 1,
  "name": "Steel Vengeance",
  "slug": "steel-vengeance",
  "description": "World's first hyper-hybrid coaster...",
  "park": { /* Park details */ },
  "category": { /* Rich Choice Object */ },
  "status": { /* Rich Choice Object */ },
  "manufacturer": { /* Company details */ },
  "designer": { /* Company details */ },
  "ride_model": {
    "name": "I-Box Track",
    "description": "Steel track on wooden structure"
  },
  "opening_date": "2018-05-05",
  "stats": {
    "height_ft": "205.00",
    "speed_mph": "74.00",
    "length_ft": "5740.00",
    "inversions": 4,
    "ride_time_seconds": 150,
    "track_type": "Hybrid",
    "track_material": {
      "value": "HYBRID",
      "label": "Hybrid",
      "metadata": {
        "color": "orange",
        "icon": "layers"
      }
    }
  },
  "photos": [ /* Photo array */ ],
  "reviews": [ /* Recent reviews */ ],
  "rankings": {
    "global_rank": 1,
    "category_rank": 1,
    "park_rank": 1
  }
}
```

### User Accounts API

#### User Profile
```javascript
GET /api/v1/accounts/profile/

// Response
{
  "user_id": "1234",
  "username": "thrillseeker",
  "email": "user@example.com",
  "role": {
    "value": "USER",
    "label": "User",
    "metadata": {
      "color": "blue",
      "icon": "user",
      "permissions": ["create_content", "create_reviews"]
    }
  },
  "theme_preference": {
    "value": "dark",
    "label": "Dark",
    "metadata": {
      "color": "gray",
      "icon": "moon",
      "preview_colors": {
        "background": "#1f2937",
        "text": "#f9fafb"
      }
    }
  },
  "profile": {
    "display_name": "Thrill Seeker",
    "avatar_url": "https://imagedelivery.net/...",
    "bio": "Love roller coasters!",
    "coaster_credits": 150,
    "stats": { /* User statistics */ }
  }
}
```

#### User Notifications
```javascript
GET /api/v1/accounts/notifications/

// Response
{
  "count": 25,
  "unread_count": 5,
  "results": [
    {
      "id": 1,
      "notification_type": {
        "value": "submission_approved",
        "label": "Submission Approved",
        "metadata": {
          "color": "green",
          "icon": "check-circle",
          "category": "submission"
        }
      },
      "title": "Your submission has been approved!",
      "message": "Your photo submission for Cedar Point has been approved.",
      "priority": {
        "value": "normal",
        "label": "Normal",
        "metadata": {
          "urgency_level": 2
        }
      },
      "is_read": false,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ]
}
```

## Data Models

### TypeScript Interfaces

```typescript
// Core Models
interface Park {
  id: number;
  name: string;
  slug: string;
  description: string;
  park_type: RichChoice;
  status: RichChoice;
  opening_date: string;
  size_acres: string;
  location: ParkLocation;
  operator: Company;
  property_owner?: Company;
  stats: ParkStats;
  photos: Photo[];
  areas: ParkArea[];
}

interface Ride {
  id: number;
  name: string;
  slug: string;
  description: string;
  park: Park;
  category: RichChoice;
  status: RichChoice;
  manufacturer?: Company;
  designer?: Company;
  ride_model?: RideModel;
  opening_date: string;
  stats?: RideStats;
  photos: Photo[];
}

interface User {
  user_id: string;
  username: string;
  email: string;
  role: RichChoice;
  theme_preference: RichChoice;
  privacy_level: RichChoice;
  profile: UserProfile;
}

interface UserProfile {
  profile_id: string;
  display_name: string;
  avatar_url: string;
  avatar_variants: {
    thumbnail: string;
    avatar: string;
    large: string;
  };
  bio: string;
  coaster_credits: number;
  dark_ride_credits: number;
  flat_ride_credits: number;
  water_ride_credits: number;
}

// Location Models
interface ParkLocation {
  street_address: string;
  city: string;
  state: string;
  country: string;
  postal_code: string;
  coordinates: {
    latitude: number;
    longitude: number;
  };
  timezone: string;
}

// Media Models
interface Photo {
  id: number;
  photo_type: string;
  image_url: string;
  variants: {
    thumbnail: string;
    medium: string;
    large: string;
    [key: string]: string;
  };
  attribution: string;
  caption: string;
  is_primary: boolean;
}

// Company Models
interface Company {
  id: number;
  name: string;
  slug: string;
  roles: RichChoice[];
  description: string;
  website: string;
  founded_year?: number;
  headquarters?: CompanyHeadquarters;
}
```

## Error Handling

### Standard Error Response

```javascript
// 400 Bad Request
{
  "error": "validation_error",
  "message": "Invalid input data",
  "details": {
    "field_name": ["This field is required."]
  }
}

// 401 Unauthorized
{
  "error": "authentication_required",
  "message": "Authentication credentials were not provided."
}

// 403 Forbidden
{
  "error": "permission_denied",
  "message": "You do not have permission to perform this action."
}

// 404 Not Found
{
  "error": "not_found",
  "message": "The requested resource was not found."
}

// 500 Internal Server Error
{
  "error": "server_error",
  "message": "An unexpected error occurred. Please try again later."
}
```

### Error Handling in Frontend

```javascript
async function fetchPark(slug) {
  try {
    const response = await fetch(`/api/v1/parks/${slug}/`);
    
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.message || 'Request failed');
    }
    
    return await response.json();
  } catch (error) {
    console.error('Failed to fetch park:', error);
    // Handle error appropriately in UI
    throw error;
  }
}
```

## Integration Examples

### React Hook for Rich Choices

```javascript
import { useState, useEffect } from 'react';

function useRichChoices(domain, choiceGroup) {
  const [choices, setChoices] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function fetchChoices() {
      try {
        const response = await fetch(`/api/v1/choices/${domain}/${choiceGroup}/`);
        const data = await response.json();
        setChoices(data);
      } catch (error) {
        console.error('Failed to fetch choices:', error);
      } finally {
        setLoading(false);
      }
    }
    
    fetchChoices();
  }, [domain, choiceGroup]);
  
  return { choices, loading };
}

// Usage
function UserRoleSelect({ value, onChange }) {
  const { choices, loading } = useRichChoices('accounts', 'user_roles');
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <select value={value} onChange={onChange}>
      {choices.map(choice => (
        <option key={choice.value} value={choice.value}>
          {choice.label}
        </option>
      ))}
    </select>
  );
}
```

### Choice Display Component

```javascript
function ChoiceDisplay({ choice, showIcon = true, showDescription = false }) {
  const { value, label, description, metadata } = choice;
  
  return (
    <span className={`choice-display ${metadata.css_class}`}>
      {showIcon && metadata.icon && (
        <Icon name={metadata.icon} color={metadata.color} />
      )}
      <span className="choice-label">{label}</span>
      {showDescription && description && (
        <span className="choice-description">{description}</span>
      )}
    </span>
  );
}
```

### API Client with Rich Choice Support

```javascript
class ThrillWikiAPI {
  constructor(baseURL, token) {
    this.baseURL = baseURL;
    this.token = token;
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const headers = {
      'Content-Type': 'application/json',
      'Authorization': `Token ${this.token}`,
      ...options.headers
    };
    
    const response = await fetch(url, { ...options, headers });
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'Request failed');
    }
    
    return response.json();
  }
  
  // Parks
  async getParks(filters = {}) {
    const params = new URLSearchParams(filters);
    return this.request(`/parks/?${params}`);
  }
  
  async getPark(slug) {
    return this.request(`/parks/${slug}/`);
  }
  
  // Rides
  async getRides(filters = {}) {
    const params = new URLSearchParams(filters);
    return this.request(`/rides/?${params}`);
  }
  
  async getRide(slug) {
    return this.request(`/rides/${slug}/`);
  }
  
  // Choices
  async getChoices(domain, choiceGroup) {
    return this.request(`/choices/${domain}/${choiceGroup}/`);
  }
  
  // User
  async getCurrentUser() {
    return this.request('/accounts/profile/');
  }
  
  async updateUserPreferences(preferences) {
    return this.request('/accounts/preferences/', {
      method: 'PATCH',
      body: JSON.stringify(preferences)
    });
  }
}
```

## Best Practices

### 1. Always Use Rich Choice Objects
- Never hardcode choice values or labels
- Always fetch choice metadata for proper UI rendering
- Use choice metadata for styling and icons

### 2. Handle Loading States
- Show loading indicators while fetching data
- Implement proper error boundaries
- Cache choice data to avoid repeated requests

### 3. Responsive Design
- Use photo variants for different screen sizes
- Implement proper image lazy loading
- Consider mobile-first design patterns

### 4. Performance Optimization
- Use pagination for large datasets
- Implement proper caching strategies
- Minimize API requests with efficient data fetching

### 5. Error Handling
- Always handle API errors gracefully
- Provide meaningful error messages to users
- Implement retry logic for transient failures

## API Versioning

The API uses URL-based versioning (`/api/v1/`). When breaking changes are introduced, a new version will be created (`/api/v2/`). The current version (v1) will be maintained for backward compatibility.

## Rate Limiting

API requests are rate-limited to prevent abuse:
- **Authenticated users**: 1000 requests per hour
- **Anonymous users**: 100 requests per hour
- **Bulk operations**: Special limits apply

Rate limit headers are included in responses:
```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642694400
```

## Support

For API support and questions:
- **Documentation**: https://api.thrillwiki.com/api/schema/swagger-ui/
- **GitHub Issues**: https://github.com/thrillwiki/api/issues
- **Email**: api-support@thrillwiki.com

---

**Note**: This documentation is automatically updated when the API changes. Always refer to the latest version for accurate information.
