# ST-17589: Next Clip Categories KVP Implementation Plan

## Overview

**Ticket**: ST-17589 - Add Next Clip Categories KVP for Clip Ad Requests
**Purpose**: Support Sliide's ad positioning requirements by adding a new default KVP (`stNextClipCategories`) for Clip Ad Requests that includes the categories of the next clip rather than just the current clip.

## Business Context

### Problem Statement
Some clients have restrictions preventing them from displaying ads next to particular content. This includes:
- Ads displayed **after** restricted content
- Ads displayed **before** restricted content

Currently, we only send the current clip's categories (`stClipCategories`), which doesn't allow advertisers to suppress ads based on what content will be shown next.

### Solution
Add a new KVP called `stNextClipCategories` that contains the categories of the clip immediately following the current one, allowing advertisers to make informed decisions about ad placement.

## Technical Analysis

### Current Implementation

#### 1. Ad Configuration Generation
- **Location**: `/src/configuration/models/adConfig.ts:29-96` (getClipAdConfig function)
- **Current KVPs**:
  - For Storyteller ads: `collection`, `clipCategories`
  - For integrating app ads: `collection`, `clipCategories` (as customTargeting)

#### 2. Clip Ad Creation
- **Location**: `/src/ads/models/clipAd.ts:26-34` (constructor)
- **Process**: 
  - ClipAd is created with reference to `clipBeforeAd`
  - Calls `getClipAdConfig` with current clip data
  - Creates amp-ad element with current clip's categories

#### 3. Clips Navigation Service
- **Location**: `/src/clipsView/abstract/clipsNavService.ts`
- **Key Properties**:
  - `allSlides_`: Array of all clips and ads
  - `appendedSlides_`: Currently rendered slides (max 5)
  - `activeSlideIndex_`: Current position in feed

### Key Findings

1. **Ad Insertion Point**: Ads are inserted AFTER clips at configured frequency
2. **Data Available**: When creating a ClipAd, we have:
   - Reference to the clip before the ad (`clipBeforeAd`)
   - Collection context
   - Access to the full clips array via ClipsNavService
3. **Challenge**: Need to identify the next clip in sequence, accounting for:
   - Ads in the feed
   - End of collection scenarios
   - Category-filtered views

## Implementation Strategy

### Phase 1: Core Implementation

#### Step 1: Extend ClipAd Model
**File**: `/src/ads/models/clipAd.ts`

Add property and method to track next clip:
```typescript
export class ClipAd extends AdPage {
  // ... existing properties
  public clipAfterAd: ClipWithIndexInCategory | null;
  
  public setClipAfterAd(nextClip: ClipWithIndexInCategory | null) {
    this.clipAfterAd = nextClip;
  }
}
```

#### Step 2: Update Ad Config Function
**File**: `/src/configuration/models/adConfig.ts`

Modify `getClipAdConfig` to accept and use next clip data:
```typescript
export const getClipAdConfig = ({
  clip,
  collection,
  nextClip,  // NEW parameter
}: {
  clip: Clip;
  collection: string;
  nextClip?: Clip | null;  // NEW parameter
}) => {
  // ... existing code
  
  if (dataStorageService.settings?.adsFrom === AdServeMethod.storyteller) {
    // ... existing code
    const queryParams = new URLSearchParams(
      canCustomiseTargeting
        ? {
            collection,
            clipCategories: clip.categories.join(','),
            // NEW: Add next clip categories
            nextClipCategories: nextClip?.categories?.join(',') || '',
          }
        : {}
    );
  }
  
  if (dataStorageService.settings?.adsFrom === AdServeMethod.integratingApp) {
    // ... existing code
    const defaultTargeting: AdTargeting = canCustomiseTargeting
      ? {
          collection,
          clipCategories: clip.clipCategories
            .map((clipCategory) => clipCategory.externalId)
            .filter(Boolean)
            .join(','),
          // NEW: Add next clip categories as KVP
          stNextClipCategories: nextClip?.clipCategories
            ?.map((category) => category.externalId)
            .filter(Boolean)
            .join(',') || '',
        }
      : {};
  }
}
```

#### Step 3: Update ClipAd Creation
**File**: `/src/ads/models/clipAd.ts`

Modify constructor and `createAdElement_` to handle next clip:
```typescript
constructor(
  clipBeforeAd: ClipWithIndexInCategory, 
  collection: string,
  clipAfterAd?: ClipWithIndexInCategory | null  // NEW parameter
) {
  super();
  this.clipBeforeAd = clipBeforeAd;
  this.clipAfterAd = clipAfterAd || null;  // NEW
  // ... rest of constructor
}

private createAdElement_() {
  const adConfig = getClipAdConfig({
    clip: this.clipBeforeAd,
    collection: this.collection,
    nextClip: this.clipAfterAd,  // NEW: Pass next clip
  });
  // ... rest of method
}
```

#### Step 4: Update ClipsNavService Ad Creation
**File**: `/src/clipsView/abstract/clipsNavService.ts`

Modify ad creation logic to identify and pass next clip:

```typescript
private createAdSlide_(clipBeforeAd: ClipWithIndexInCategory): ClipAd {
  // Find the next clip after this ad position
  const nextClipIndex = clipBeforeAd.indexInCollection + 1;
  const nextClip = this.collectionClips_[nextClipIndex] || null;
  
  // Create ad with next clip reference
  const ad = new ClipAd(clipBeforeAd, this.collection_, nextClip);
  
  return ad;
}
```

### Phase 2: Edge Cases & Testing

#### Edge Cases to Handle

1. **Last Clip in Collection**
   - When ad appears after the last clip
   - Solution: Pass `null` or empty string for `stNextClipCategories`

2. **Multiple Ads in Sequence**
   - When ad frequency results in consecutive ads
   - Solution: Look ahead to find the next actual clip

3. **Category Filtering**
   - When viewing clips filtered by category
   - Solution: Use the filtered clip sequence

4. **Loop Mode**
   - When clips loop back to beginning
   - Solution: Next clip after last is the first clip

#### Test Scenarios

1. **Standard Flow**
   - Clip 1 → Clip 2 → Ad → Clip 3
   - Ad should have Clip 3's categories as `stNextClipCategories`

2. **End of Collection**
   - Clip N-1 → Clip N → Ad → (End)
   - Ad should have empty `stNextClipCategories`

3. **Multiple Categories**
   - Verify all category external IDs are included
   - Format: comma-separated string

4. **Privacy Mode**
   - When `enableAdTracking` is false
   - Verify `stNextClipCategories` is not sent

### Phase 3: Integration Testing

#### Manual Testing Steps

1. **Setup Test Environment**
   ```javascript
   // Set ad configuration
   localStorage.setItem('API_ENVIRONMENT', 'staging');
   // Enable integrating app ads to see KVPs
   ```

2. **Verify KVP in Ad Request**
   - Open Network tab in DevTools
   - Navigate through clips
   - Inspect ad requests for `stNextClipCategories` parameter

3. **Test with Demo App**
   - Use demo site at localhost:8080
   - Configure ad frequency to 2 (ad every 2 clips)
   - Verify correct categories are sent

#### Automated Testing

Create unit tests for:
1. `getClipAdConfig` with next clip parameter
2. ClipAd constructor with next clip
3. ClipsNavService ad creation with next clip identification

## Implementation Checklist

### Core Implementation
- [ ] Extend ClipAd model with `clipAfterAd` property
- [ ] Update `getClipAdConfig` function signature
- [ ] Add `stNextClipCategories` to Storyteller ad requests
- [ ] Add `stNextClipCategories` to integrating app custom targeting
- [ ] Update ClipAd constructor to accept next clip
- [ ] Modify ClipsNavService to identify next clip when creating ads

### Edge Cases
- [ ] Handle last clip in collection
- [ ] Handle multiple ads in sequence
- [ ] Handle category-filtered views
- [ ] Handle loop mode if applicable

### Testing
- [ ] Unit tests for getClipAdConfig
- [ ] Unit tests for ClipAd with next clip
- [ ] Integration test with demo app
- [ ] Verify KVP in network requests
- [ ] Test privacy mode (no tracking)

### Documentation
- [ ] Update inline code comments
- [ ] Update ads documentation if needed
- [ ] Add to CLAUDE.md if significant

## Risk Assessment

### Low Risk
- Changes are additive (new parameter, backward compatible)
- Doesn't affect existing `stClipCategories` KVP
- Only affects ad requests, not clip playback

### Potential Issues
1. **Performance**: Looking up next clip adds minimal overhead
2. **Memory**: Storing additional reference in ClipAd (negligible)
3. **Compatibility**: Older integrations unaffected (new KVP is optional)

## Success Criteria

1. **Functional**: `stNextClipCategories` KVP appears in all clip ad requests
2. **Accurate**: KVP contains correct categories for the next clip
3. **Edge Cases**: Handles end of collection and special cases gracefully
4. **Backward Compatible**: Existing integrations continue to work
5. **Performance**: No noticeable impact on clip player performance

## Timeline Estimate

- **Core Implementation**: 2-3 hours
- **Edge Case Handling**: 1-2 hours
- **Testing & Validation**: 2-3 hours
- **Total**: ~6-8 hours

## Notes

- The KVP should contain external IDs (like `stClipCategories` does)
- Empty or null categories should result in empty string, not undefined
- This is SDK-only work, no backend changes required
- Follow existing patterns for consistency