# Migration Example: Using the Pluggable Architecture

This document provides concrete examples of how to use the new pluggable architecture, demonstrating both backward compatibility and new patterns.

## Table of Contents

1. [Using the Adapter for Backward Compatibility](#using-the-adapter-for-backward-compatibility)
2. [Writing New Code with Metadata Pattern](#writing-new-code-with-metadata-pattern)
3. [Creating a New Application Type](#creating-a-new-application-type)
4. [Migration Path](#migration-path)

## Using the Adapter for Backward Compatibility

The adapter layer allows existing code to work unchanged while the codebase gradually migrates to the new architecture.

### Example: Existing Code Continues to Work

```typescript
import { wrapGame, wrapSession } from './routes/games/gameAdapter';
import type { Game } from './routes/games/types';

// Load existing game from database/storage
const loadedGame: Game = loadGameFromDatabase(gameId);

// Wrap the game object to enable backward compatibility
const game = wrapGame(loadedGame);

// Old code continues to work unchanged!
game.turn = { color: "red", roll: 7 };           // Proxied to game.metadata.turn
game.players["red"].resources = 5;               // Proxied to game.metadata.players["red"]

// Session operations also work
const session = game.sessions["session123"];
session.color = "blue";                          // Proxied to session.metadata.color
session.player = game.players["blue"];           // Proxied to session.metadata.player

console.log("Player color:", session.color);     // Reads from session.metadata.color
console.log("Turn data:", game.turn);            // Reads from game.metadata.turn
```

### How the Proxy Works

The adapter uses JavaScript Proxy to intercept property access:

```typescript
// Reading game-specific properties
const color = session.color;
// Behind the scenes: session.metadata?.color

// Writing game-specific properties
session.color = "red";
// Behind the scenes: session.metadata = { ...session.metadata, color: "red" }

// Infrastructure properties work normally
const sessionId = session.id;          // Direct access (not proxied)
const isLive = session.live;           // Direct access (not proxied)
```

## Writing New Code with Metadata Pattern

New code should use the metadata pattern directly for clean separation of concerns.

### Example: Session Color Selection (New Pattern)

```typescript
import type { GameSession, GameSessionMetadata } from './routes/games/gameMetadata';
import { updateSessionMetadata, getSessionMetadata } from './routes/room/helpers';

function handleColorSelection(session: GameSession, color: string): void {
  // Update game-specific metadata
  updateSessionMetadata<GameSessionMetadata>(session, {
    color: color
  });

  // Infrastructure fields remain separate
  session.live = true;
  session.lastActive = Date.now();
}

function getPlayerColor(session: GameSession): string | undefined {
  const metadata = getSessionMetadata<GameSessionMetadata>(session);
  return metadata?.color;
}
```

### Example: Getting Participants with Game Data

```typescript
import { getParticipants } from './routes/room/helpers';
import type { GameRoom, GameSessionMetadata } from './routes/games/gameMetadata';

function getGameParticipants(room: GameRoom) {
  // Get base participant data (reusable)
  const baseParticipants = getParticipants(room.sessions);

  // Extend with game-specific data
  return baseParticipants.map(p => {
    const session = room.sessions[p.session_id];
    const metadata = session.metadata as GameSessionMetadata | undefined;

    return {
      ...p,                           // Base participant data
      color: metadata?.color || null, // Game-specific data
    };
  });
}
```

### Example: Room State Management

```typescript
import type { GameRoom, GameRoomMetadata } from './routes/games/gameMetadata';
import { updateRoomMetadata, getRoomMetadata } from './routes/room/helpers';

function initializeGameRoom(room: GameRoom): void {
  // Initialize game-specific metadata
  updateRoomMetadata<GameRoomMetadata>(room, {
    players: {},
    turn: { color: undefined, actions: [] },
    placements: { corners: [], roads: [] },
    chat: [],
    developmentCards: [],
  });

  // Infrastructure state remains separate
  room.state = "lobby";
}

function updateTurn(room: GameRoom, color: string, roll: number): void {
  const metadata = getRoomMetadata<GameRoomMetadata>(room);
  if (!metadata) return;

  // Update game state in metadata
  metadata.turn = {
    ...metadata.turn,
    color,
    roll,
  };

  // Infrastructure state changes separately
  room.state = "playing";
}
```

## Creating a New Application Type

The infrastructure can be reused for any multi-user WebRTC application.

### Example: Creating a Whiteboard Application

```typescript
// Step 1: Define your metadata types
// File: server/routes/whiteboard/whiteboardMetadata.ts

import type { Session, Room } from '../room/types';
import type { Player } from './whiteboardTypes';

export interface WhiteboardSessionMetadata {
  tool: "pen" | "eraser" | "shape";
  color: string;
  brushSize: number;
  selectedObject?: string;
}

export interface WhiteboardRoomMetadata {
  canvas: CanvasObject[];
  history: HistoryEntry[];
  locked: boolean;
}

export type WhiteboardSession = Session<WhiteboardSessionMetadata>;
export type WhiteboardRoom = Room<WhiteboardRoomMetadata>;
```

```typescript
// Step 2: Use the reusable infrastructure
// File: server/routes/whiteboard/handlers.ts

import type { WhiteboardRoom, WhiteboardSession, WhiteboardSessionMetadata } from './whiteboardMetadata';
import {
  getParticipants,
  updateSessionMetadata,
  updateRoomMetadata
} from '../room/helpers';

export function handleToolChange(
  session: WhiteboardSession,
  tool: "pen" | "eraser" | "shape"
): void {
  updateSessionMetadata<WhiteboardSessionMetadata>(session, { tool });
}

export function getWhiteboardParticipants(room: WhiteboardRoom) {
  const baseParticipants = getParticipants(room.sessions);

  // Add whiteboard-specific data
  return baseParticipants.map(p => {
    const session = room.sessions[p.session_id];
    const metadata = session.metadata;

    return {
      ...p,
      tool: metadata?.tool || "pen",
      color: metadata?.color || "#000000",
      brushSize: metadata?.brushSize || 5,
    };
  });
}

export function addCanvasObject(room: WhiteboardRoom, object: CanvasObject): void {
  if (!room.metadata) {
    updateRoomMetadata(room, {
      canvas: [],
      history: [],
      locked: false,
    });
  }

  room.metadata!.canvas.push(object);
  room.metadata!.history.push({
    type: 'add',
    timestamp: Date.now(),
    object,
  });
}
```

```typescript
// Step 3: Reuse MediaControl component (client-side)
// File: client/src/WhiteboardApp.tsx

import { MediaAgent, MediaControl } from './MediaControl';
import type { Session } from './GlobalContext';

function WhiteboardApp() {
  const [session, setSession] = useState<Session | null>(null);
  const [peers, setPeers] = useState<Record<string, Peer>>({});

  return (
    <>
      {/* Reuse MediaAgent - no changes needed! */}
      <MediaAgent
        session={session}
        socketUrl={socketUrl}
        peers={peers}
        setPeers={setPeers}
      />

      {/* Reuse MediaControl - no changes needed! */}
      {participants.map(p => (
        <MediaControl
          key={p.session_id}
          peer={peers[p.session_id]}
          isSelf={p.session_id === session?.id}
          sendJsonMessage={sendJsonMessage}
        />
      ))}

      {/* Your whiteboard-specific UI */}
      <Canvas participants={participants} />
    </>
  );
}
```

## Migration Path

### Phase 1: Add Metadata Layer (✅ Complete)

The infrastructure and metadata types are now defined. Code can use either pattern:

```typescript
// Old pattern (via adapter)
session.color = "red";

// New pattern (direct metadata access)
updateSessionMetadata(session, { color: "red" });
```

### Phase 2: Migrate Functions One by One

Gradually update functions to use metadata pattern:

```typescript
// Before: Mixed concerns
function setPlayerColor(session: any, color: string): void {
  session.color = color;  // Direct property access
  session.live = true;
}

// After: Separated concerns
function setPlayerColor(session: GameSession, color: string): void {
  updateSessionMetadata<GameSessionMetadata>(session, { color });  // Metadata
  session.live = true;  // Infrastructure
}
```

### Phase 3: Update Message Handlers

Update WebSocket message handlers to work with metadata:

```typescript
// Before
case "set": {
  if (data.field === "color") {
    session.color = data.value;
  }
  break;
}

// After
case "set": {
  if (data.field === "color") {
    updateSessionMetadata<GameSessionMetadata>(session, {
      color: data.value
    });
  }
  break;
}
```

### Phase 4: Remove Adapters (Future)

Once all code is migrated, remove the adapter layer and use pure metadata:

```typescript
// Pure metadata architecture (no adapters)
const session: GameSession = {
  // Infrastructure fields
  id: "abc123",
  name: "Alice",
  live: true,
  has_media: true,
  lastActive: Date.now(),

  // Application-specific data
  metadata: {
    color: "red",
    player: {...},
    resources: 5,
  }
};
```

## Benefits of the New Architecture

### 1. Reusability

The same infrastructure works for different applications:

```typescript
// Game application
const gameRoom: Room<GameRoomMetadata> = {...};

// Whiteboard application
const whiteboardRoom: Room<WhiteboardRoomMetadata> = {...};

// Chat application
const chatRoom: Room<ChatRoomMetadata> = {...};

// All use the same getParticipants, session management, WebRTC signaling!
```

### 2. Type Safety

TypeScript ensures correct metadata usage:

```typescript
const session: GameSession = {...};

// ✅ Type-safe
updateSessionMetadata<GameSessionMetadata>(session, { color: "red" });

// ❌ TypeScript error: 'invalidField' doesn't exist
updateSessionMetadata<GameSessionMetadata>(session, { invalidField: "value" });
```

### 3. Clear Separation

Infrastructure and application logic are clearly separated:

```typescript
// Infrastructure (works for any app)
import { getParticipants, updateSessionActivity } from './room/helpers';

// Application-specific (game logic)
import { GameSession, GameRoom } from './games/gameMetadata';
import { calculatePoints, updateTurn } from './games/gameLogic';
```

### 4. Easy Testing

Each layer can be tested independently:

```typescript
// Test infrastructure
describe('getParticipants', () => {
  it('returns participant list from any session type', () => {
    const sessions = { /* any session structure */ };
    const participants = getParticipants(sessions);
    expect(participants).toHaveLength(2);
  });
});

// Test game logic
describe('calculatePoints', () => {
  it('calculates game points from metadata', () => {
    const room: GameRoom = { /* ... */ };
    calculatePoints(room);
    expect(room.metadata?.players['red'].points).toBe(10);
  });
});
```

## Common Patterns

### Pattern 1: Extending Participant Lists

```typescript
function getExtendedParticipants<TMetadata extends Record<string, any>>(
  room: Room<any>,
  extender: (session: Session<any>) => TMetadata
) {
  const baseParticipants = getParticipants(room.sessions);

  return baseParticipants.map(p => ({
    ...p,
    ...extender(room.sessions[p.session_id])
  }));
}

// Usage
const gameParticipants = getExtendedParticipants(gameRoom, (session) => ({
  color: session.metadata?.color || null,
  points: session.metadata?.player?.points || 0,
}));
```

### Pattern 2: Metadata Initialization

```typescript
function ensureSessionMetadata<TMetadata>(
  session: Session<TMetadata>,
  defaults: TMetadata
): void {
  if (!session.metadata) {
    session.metadata = defaults;
  }
}

// Usage
ensureSessionMetadata<GameSessionMetadata>(session, {
  color: undefined,
  player: undefined,
  resources: 0,
});
```

### Pattern 3: Safe Metadata Access

```typescript
function getMetadataField<TMetadata, K extends keyof TMetadata>(
  session: Session<TMetadata>,
  field: K,
  defaultValue: TMetadata[K]
): TMetadata[K] {
  return session.metadata?.[field] ?? defaultValue;
}

// Usage
const color = getMetadataField<GameSessionMetadata, 'color'>(
  session,
  'color',
  'gray'
);
```

## Summary

The new architecture provides:

- **Clean separation** between infrastructure and application logic
- **Reusable components** (MediaControl, Room helpers, WebRTC signaling)
- **Type safety** with generic TypeScript types
- **Backward compatibility** via adapter pattern
- **Extensibility** for new application types

By following these patterns, you can create new applications that leverage the existing WebRTC and Room infrastructure without modification.
