# Migration Guide: Using Generated API Client

This guide shows how to migrate from manual Axios API calls to the auto-generated OpenAPI client.

## Why Migrate?

✅ **Type Safety** - Catch API schema mismatches at compile time  
✅ **Auto-completion** - Full IDE support for all API methods  
✅ **Automatic Updates** - Regenerate when backend changes  
✅ **Less Code** - No manual type definitions needed  
✅ **Fewer Bugs** - Schema validation prevents runtime errors

## Quick Start

### 1. Import Services Instead of apiClient

**Before (Manual):**
```typescript
import { apiClient } from '@/lib/api-client';
```

**After (Generated):**
```typescript
import { AuthService, PacksService, ActionsService } from '@/api';
```

### 2. Use Service Methods Instead of HTTP Verbs

**Before (Manual):**
```typescript
const response = await apiClient.get('/api/v1/packs', {
  params: { page: 1, page_size: 50 }
});
```

**After (Generated):**
```typescript
const response = await PacksService.listPacks({
  page: 1,
  pageSize: 50
});
```

## Real-World Examples

### Authentication (AuthContext)

**Before:**
```typescript
// src/contexts/AuthContext.tsx (OLD)
import { apiClient } from "@/lib/api-client";
import type { User, LoginRequest, LoginResponse, ApiResponse } from "@/types/api";

const login = async (credentials: LoginRequest) => {
  const response = await apiClient.post<ApiResponse<LoginResponse>>(
    "/auth/login",
    credentials
  );
  
  const { access_token, refresh_token } = response.data.data;
  localStorage.setItem("access_token", access_token);
  localStorage.setItem("refresh_token", refresh_token);
};

const loadUser = async () => {
  const response = await apiClient.get<ApiResponse<User>>("/auth/me");
  setUser(response.data.data);
};
```

**After:**
```typescript
// src/contexts/AuthContext.tsx (NEW)
import { AuthService } from "@/api";
import type { UserInfo } from "@/api"; // Types are generated!

const login = async (credentials: { login: string; password: string }) => {
  const response = await AuthService.login({
    requestBody: credentials
  });
  
  const { access_token, refresh_token } = response.data;
  localStorage.setItem("access_token", access_token);
  localStorage.setItem("refresh_token", refresh_token);
};

const loadUser = async () => {
  const response = await AuthService.getCurrentUser();
  setUser(response.data);
};
```

### Pack Management

**Before:**
```typescript
// Manual pack API calls
import { apiClient } from '@/lib/api-client';

interface Pack {
  id: number;
  ref: string;
  label: string;
  // ... manual type definition
}

const fetchPacks = async () => {
  const response = await apiClient.get<ApiResponse<PaginatedResponse<Pack>>>(
    '/api/v1/packs',
    { params: { page: 1, page_size: 50 } }
  );
  return response.data.data;
};

const createPack = async (data: any) => {
  const response = await apiClient.post('/api/v1/packs', data);
  return response.data.data;
};

const updatePack = async (ref: string, data: any) => {
  const response = await apiClient.put(`/api/v1/packs/${ref}`, data);
  return response.data.data;
};

const deletePack = async (ref: string) => {
  await apiClient.delete(`/api/v1/packs/${ref}`);
};
```

**After:**
```typescript
// Generated pack service (auto-typed!)
import { PacksService } from '@/api';
import type { CreatePackRequest, UpdatePackRequest } from '@/api';

const fetchPacks = async () => {
  const response = await PacksService.listPacks({
    page: 1,
    pageSize: 50
  });
  return response.data; // Already typed as PaginatedResponse<PackSummary>
};

const createPack = async (data: CreatePackRequest) => {
  const response = await PacksService.createPack({ requestBody: data });
  return response.data; // Already typed as PackResponse
};

const updatePack = async (ref: string, data: UpdatePackRequest) => {
  const response = await PacksService.updatePack({ ref, requestBody: data });
  return response.data;
};

const deletePack = async (ref: string) => {
  await PacksService.deletePack({ ref });
};
```

### React Query Integration

**Before:**
```typescript
import { useQuery, useMutation } from '@tanstack/react-query';
import { apiClient } from '@/lib/api-client';

const { data } = useQuery({
  queryKey: ['packs'],
  queryFn: async () => {
    const response = await apiClient.get('/api/v1/packs');
    return response.data.data;
  }
});

const mutation = useMutation({
  mutationFn: async (data: any) => {
    const response = await apiClient.post('/api/v1/packs', data);
    return response.data.data;
  }
});
```

**After:**
```typescript
import { useQuery, useMutation } from '@tanstack/react-query';
import { PacksService } from '@/api';
import type { CreatePackRequest } from '@/api';

const { data } = useQuery({
  queryKey: ['packs'],
  queryFn: () => PacksService.listPacks({ page: 1, pageSize: 50 })
});

const mutation = useMutation({
  mutationFn: (data: CreatePackRequest) => 
    PacksService.createPack({ requestBody: data })
});
```

### Form Submissions

**Before:**
```typescript
const handleSubmit = async (formData: any) => {
  try {
    const response = await apiClient.post('/api/v1/packs', {
      name: formData.name,      // ❌ Wrong field
      system: formData.system   // ❌ Wrong field
    });
    console.log(response.data.data);
  } catch (error) {
    // Runtime error when schema doesn't match!
  }
};
```

**After:**
```typescript
import type { CreatePackRequest } from '@/api';

const handleSubmit = async (formData: CreatePackRequest) => {
  try {
    const response = await PacksService.createPack({
      requestBody: {
        ref: formData.ref,              // ✅ TypeScript enforces correct fields
        label: formData.label,          // ✅ Compile-time validation
        is_standard: formData.is_standard
      }
    });
    console.log(response.data);
  } catch (error) {
    // Caught at compile time!
  }
};
```

### Error Handling

**Before:**
```typescript
import { AxiosError } from 'axios';

try {
  await apiClient.get('/api/v1/packs/unknown');
} catch (error) {
  if (error instanceof AxiosError) {
    console.error(error.response?.status);
  }
}
```

**After:**
```typescript
import { ApiError } from '@/api';

try {
  await PacksService.getPack({ ref: 'unknown' });
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`${error.status}: ${error.message}`);
    console.error(error.body); // Response body
  }
}
```

## Migration Checklist

### Phase 1: Setup ✅ (Already Done)
- [x] Install `openapi-typescript-codegen`
- [x] Add `generate:api` script to `package.json`
- [x] Generate initial API client
- [x] Create `src/lib/api-config.ts` for configuration
- [x] Import config in `src/main.tsx`

### Phase 2: Migrate Core Files
- [ ] Update `src/contexts/AuthContext.tsx` to use `AuthService`
- [ ] Update `src/types/api.ts` to re-export generated types
- [ ] Create custom hooks using generated services

### Phase 3: Migrate Pages
- [ ] Update all pack-related pages (`PacksPage`, `PackCreatePage`, etc.)
- [ ] Update all action-related pages
- [ ] Update all rule-related pages
- [ ] Update all execution-related pages
- [ ] Update all event-related pages

### Phase 4: Cleanup
- [ ] Remove manual API type definitions from `src/types/api.ts`
- [ ] Remove unused manual API calls
- [ ] Update all import statements
- [ ] Run TypeScript type checking: `npm run build`
- [ ] Test all workflows end-to-end

## Common Patterns

### Pattern 1: Create Custom Hooks

```typescript
// src/hooks/usePacks.ts
import { useQuery } from '@tanstack/react-query';
import { PacksService } from '@/api';

export const usePacks = (page = 1, pageSize = 50) => {
  return useQuery({
    queryKey: ['packs', page, pageSize],
    queryFn: () => PacksService.listPacks({ page, pageSize })
  });
};

export const usePack = (ref: string) => {
  return useQuery({
    queryKey: ['pack', ref],
    queryFn: () => PacksService.getPack({ ref }),
    enabled: !!ref
  });
};
```

### Pattern 2: Type-Safe Form Handling

```typescript
import { useForm } from 'react-hook-form';
import type { CreatePackRequest } from '@/api';

const PackForm = () => {
  const { register, handleSubmit } = useForm<CreatePackRequest>();
  
  const onSubmit = async (data: CreatePackRequest) => {
    await PacksService.createPack({ requestBody: data });
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('ref')} />
      <input {...register('label')} />
      <button type="submit">Create</button>
    </form>
  );
};
```

### Pattern 3: Optimistic Updates

```typescript
const mutation = useMutation({
  mutationFn: (data: UpdatePackRequest) =>
    PacksService.updatePack({ ref: packRef, requestBody: data }),
  onMutate: async (newData) => {
    await queryClient.cancelQueries({ queryKey: ['pack', packRef] });
    const previous = queryClient.getQueryData(['pack', packRef]);
    queryClient.setQueryData(['pack', packRef], newData);
    return { previous };
  },
  onError: (err, variables, context) => {
    queryClient.setQueryData(['pack', packRef], context?.previous);
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['pack', packRef] });
  }
});
```

## Regenerating After API Changes

When the backend API changes:

1. **Start the API server:**
   ```bash
   cd crates/api
   cargo run --bin attune-api
   ```

2. **Regenerate the client:**
   ```bash
   cd web
   npm run generate:api
   ```

3. **Fix TypeScript errors:**
   ```bash
   npm run build
   ```

4. **Test your changes:**
   ```bash
   npm run dev
   ```

## Tips & Best Practices

1. **Always regenerate after backend changes** - Keep frontend in sync
2. **Use generated types** - Don't create duplicate manual types
3. **Leverage TypeScript** - Let the compiler catch schema mismatches
4. **Create custom hooks** - Wrap services in React Query hooks for reusability
5. **Don't edit generated files** - They'll be overwritten on next generation
6. **Use path aliases** - Import as `@/api` instead of `../../../api`

## Troubleshooting

### "Module not found: @/api"

Add to `vite.config.ts`:
```typescript
resolve: {
  alias: {
    '@': '/src'
  }
}
```

### "Property does not exist on type"

The backend schema changed. Regenerate the client:
```bash
npm run generate:api
```

### Token not being sent

Make sure `src/lib/api-config.ts` is imported in `src/main.tsx`:
```typescript
import './lib/api-config';
```

## Resources

- Generated API docs: `src/api/README.md`
- OpenAPI spec: `http://localhost:8080/docs` (Swagger UI)
- Backend API code: `crates/api/src/routes/`
