# 🐛 Проблема: Преждевременная отправка Unleash impression

## Описание проблемы

### ❌ Текущая реализация

```typescript
// FunnelUnleashWrapper.tsx
// Сканирует ВСЮ воронку и загружает ВСЕ флаги сразу
const allFlags = useMemo(() => {
  funnel.screens.forEach(screen => {
    // Собирает флаги со ВСЕХ экранов
    screen.variants?.forEach(...)
    screen.navigation?.rules?.forEach(...)
  });
  return Array.from(flags);
}, [funnel.screens]);

// Получает варианты для ВСЕХ флагов
allFlags.map(flag => useVariant(flag));
// ↓
// Unleash SDK эмитит "impression" для ВСЕХ флагов
// ↓
// Отправка в GA для экранов, которые пользователь НЕ ВИДЕЛ
```

### 🐛 Проблемы:

1. **Преждевременная отправка**
   ```
   Пользователь на экране 1/10
   → События отправлены для всех 10 экранов
   → Аналитика: "100% видели AB тест на экране 10"
   → Реальность: "Только 20% дошли до экрана 10"
   ```

2. **Повторная отправка при перезагрузке**
   ```
   T+0: Пользователь открыл воронку → события отправлены
   T+60s: Пользователь перезагрузил страницу → события отправлены СНОВА
   → Дубликаты в аналитике
   ```

3. **Искажение метрик**
   - Impression не отражает реальную видимость
   - Невозможно посчитать conversion rate "видел AB тест → совершил действие"
   - Завышенное количество impression событий

---

## ✅ Решение 1: Per-Screen Impressions (Рекомендуется)

### Идея:

Отправлять impression события **только когда пользователь дошел до экрана**

### Реализация:

#### 1. Создать новый хук `useScreenUnleash`:

```typescript
// src/lib/funnel/unleash/useScreenUnleash.ts
export function useScreenUnleash(flags: string[]) {
  // Получаем варианты только для флагов ТЕКУЩЕГО экрана
  const variants = flags.map(flag => useVariant(flag));

  useEffect(() => {
    // Отправляем события только когда экран рендерится
    variants.forEach(({ flag, variant }) => {
      // Проверяем не отправляли ли уже (sessionStorage)
      const storageKey = `unleash_impression_${flag}_${variant}`;
      if (!sessionStorage.getItem(storageKey)) {
        // Отправка в GA
        window.gtag("event", "experiment_impression", {...});
        sessionStorage.setItem(storageKey, "true");
      }
    });
  }, [variants]);

  return activeVariants;
}
```

#### 2. Использовать в FunnelRuntime:

```typescript
// src/components/funnel/FunnelRuntime.tsx
export function FunnelRuntime({ funnel, initialScreenId }) {
  const currentScreen = ...;
  
  // Собираем флаги только для ТЕКУЩЕГО экрана
  const currentScreenFlags = useMemo(() => {
    const flags = new Set<string>();
    
    // Из вариантов текущего экрана
    currentScreen.variants?.forEach(variant => {
      variant.conditions.forEach(condition => {
        if (condition.conditionType === "unleash") {
          flags.add(condition.unleashFlag);
        }
      });
    });
    
    // Из правил навигации текущего экрана
    currentScreen.navigation?.rules?.forEach(rule => {
      rule.conditions.forEach(condition => {
        if (condition.conditionType === "unleash") {
          flags.add(condition.unleashFlag);
        }
      });
    });
    
    return Array.from(flags);
  }, [currentScreen]);
  
  // Загружаем и отправляем impression только для текущего экрана
  const screenVariants = useScreenUnleash(currentScreenFlags);
  
  // ... rest
}
```

### ✅ Преимущества:

1. **Точная аналитика**: impression = пользователь реально увидел экран
2. **Нет дубликатов**: sessionStorage помнит отправленные события
3. **Последовательная загрузка**: флаги загружаются по мере прохождения воронки

### ⚠️ Недостатки:

1. **Прогрузка флагов**: небольшая задержка при переходе на новый экран (если флаг еще не загружен)
2. **Сложность**: нужно отслеживать флаги для каждого экрана отдельно

---

## ✅ Решение 2: Preload + Lazy Impression (Баланс)

### Идея:

Загружать все флаги заранее (как сейчас), но отправлять impression только по мере просмотра экранов

### Реализация:

#### 1. Убрать автоматическую отправку из useUnleash:

```typescript
// src/lib/funnel/unleash/useUnleash.ts
export function useUnleash({ flag }: UseUnleashProps) {
  // ... existing code
  
  // ❌ Убрать автоматическую отправку
  // useEffect(() => {
  //   unleashClient.on("impression", handleImpression);
  // }, [unleashClient]);
}
```

#### 2. Создать хук для ручной отправки:

```typescript
// src/lib/funnel/unleash/useSendImpression.ts
export function useSendImpression(
  flag: string,
  variant: string | undefined
) {
  useEffect(() => {
    if (!variant || variant === "disabled") return;
    if (typeof window === "undefined" || !window.gtag) return;

    // Проверка дубликатов
    const storageKey = `unleash_impression_${flag}_${variant}`;
    if (sessionStorage.getItem(storageKey)) return;

    // Отправка
    window.gtag("event", "experiment_impression", {
      app_name: "witlab-funnel",
      feature: flag,
      treatment: variant,
    });

    sessionStorage.setItem(storageKey, "true");
  }, [flag, variant]);
}
```

#### 3. Отправлять вручную в FunnelRuntime:

```typescript
// src/components/funnel/FunnelRuntime.tsx
export function FunnelRuntime({ funnel, initialScreenId }) {
  const { checkVariant } = useUnleashContext();
  const currentScreen = ...;
  
  // Флаги уже загружены через FunnelUnleashWrapper
  // Отправляем impression только для текущего экрана
  useEffect(() => {
    const currentFlags = extractFlagsFromScreen(currentScreen);
    
    currentFlags.forEach(flag => {
      const variant = checkVariant(flag, [], "includesAny");
      // Отправляем impression
      useSendImpression(flag, variant);
    });
  }, [currentScreen]);
}
```

### ✅ Преимущества:

1. **Быстрый переход**: флаги уже загружены заранее
2. **Точная аналитика**: impression когда экран виден
3. **Нет дубликатов**: sessionStorage

### ⚠️ Недостатки:

1. **Лишние запросы**: загружаем флаги которые пользователь может не увидеть

---

## ✅ Решение 3: Гибридный подход (Оптимальный)

### Идея:

- Загружаем флаги **только для первых N экранов** (например 3)
- По мере прохождения догружаем следующие
- Impression отправляем только при просмотре

### Реализация:

```typescript
// FunnelUnleashWrapper с preload окном
export function FunnelUnleashWrapper({ funnel, currentScreenIndex }) {
  const PRELOAD_WINDOW = 3; // Загружаем флаги для текущего + 2 следующих
  
  const preloadFlags = useMemo(() => {
    const startIndex = Math.max(0, currentScreenIndex);
    const endIndex = Math.min(funnel.screens.length, currentScreenIndex + PRELOAD_WINDOW);
    const screensToPreload = funnel.screens.slice(startIndex, endIndex);
    
    return extractFlagsFromScreens(screensToPreload);
  }, [currentScreenIndex, funnel.screens]);
  
  // Загружаем только флаги в окне preload
  const flagVariants = preloadFlags.map(flag => useVariant(flag));
  
  // Impression отправляется только в FunnelRuntime когда экран виден
}
```

### ✅ Преимущества:

1. **Оптимальная производительность**: загружаем только нужное
2. **Плавные переходы**: следующие экраны уже готовы
3. **Точная аналитика**: impression = видимость
4. **Экономия**: не загружаем флаги для экранов 7-10 если пользователь на экране 2

---

## 📊 Сравнение решений

| Критерий | Текущее | Решение 1 | Решение 2 | Решение 3 |
|----------|---------|-----------|-----------|-----------|
| **Точность аналитики** | ❌ Плохо | ✅ Отлично | ✅ Отлично | ✅ Отлично |
| **Скорость загрузки** | ✅ Быстро | ⚠️ Медленно | ✅ Быстро | ✅ Быстро |
| **Плавность переходов** | ✅ Плавно | ❌ Лаги | ✅ Плавно | ✅ Плавно |
| **Нет дубликатов** | ❌ Есть | ✅ Нет | ✅ Нет | ✅ Нет |
| **Экономия ресурсов** | ❌ Плохо | ✅ Хорошо | ❌ Плохо | ✅ Отлично |
| **Сложность реализации** | ✅ Простая | ⚠️ Средняя | ⚠️ Средняя | ❌ Сложная |

---

## 🎯 Рекомендация

### Для witlab-funnel: **Решение 2 (Preload + Lazy Impression)**

**Почему:**
1. Воронки короткие (обычно 5-10 экранов) - preload допустим
2. Плавность переходов важна для UX
3. Относительно просто реализовать
4. Исправляет главную проблему - точность аналитики

### Для длинных воронок (20+ экранов): **Решение 3 (Гибридный)**

---

## 📝 TODO для исправления

- [ ] Создать `useScreenUnleash.ts` хук
- [ ] Убрать автоотправку из `useUnleash.ts` (или сделать опциональной)
- [ ] Добавить sessionStorage для предотвращения дубликатов
- [ ] Интегрировать в `FunnelRuntime.tsx`
- [ ] Обновить тесты
- [ ] Обновить документацию

---

## 🔧 Быстрый фикс (минимальный)

Если нужно быстро исправить проблему с дубликатами при перезагрузке:

```typescript
// src/lib/funnel/unleash/useUnleash.ts
useEffect(() => {
  const handleImpression = (impressionEvent) => {
    if (impressionEvent.enabled) {
      // Проверка дубликатов
      const storageKey = `unleash_${impressionEvent.featureName}_${impressionEvent.variant}`;
      if (sessionStorage.getItem(storageKey)) {
        return; // Уже отправляли
      }
      
      // Отправка
      window.gtag("event", "experiment_impression", {...});
      
      // Помечаем
      sessionStorage.setItem(storageKey, "true");
    }
  };
  
  unleashClient.on("impression", handleImpression);
}, [unleashClient]);
```

Это хотя бы предотвратит дубликаты при перезагрузке, но не решит проблему преждевременной отправки.
