{"version":3,"file":"flag-analytics-bridge.mjs","sources":["../../../../src/lib/flags/integration/flag-analytics-bridge.tsx"],"sourcesContent":["/* eslint-disable react-refresh/only-export-components */\nimport React from 'react';\n/**\n * @fileoverview Flag analytics bridge for connecting flags to monitoring systems.\n *\n * This module provides a bridge between the feature flag system and various\n * analytics/monitoring systems. It enables correlation tracking, impact\n * measurement, and exposure analytics across library features.\n *\n * @module flags/integration/flag-analytics-bridge\n *\n * @example\n * ```typescript\n * import {\n *   FlagAnalyticsBridge,\n *   createAnalyticsBridge,\n *   useFlagAnalytics,\n * } from '@/lib/flags/integration/flag-analytics-bridge';\n *\n * // Create a bridge\n * const bridge = createAnalyticsBridge({\n *   onExposure: (event) => analytics.track('feature_exposure', event),\n *   onEvaluation: (event) => analytics.track('flag_evaluation', event),\n *   onImpact: (metric) => analytics.track('feature_impact', metric),\n * });\n *\n * // Track exposure\n * bridge.trackExposure({\n *   flagKey: 'new-checkout',\n *   variant: 'enabled',\n *   userId: 'user-123',\n * });\n *\n * // Track performance correlation\n * bridge.trackPerformanceCorrelation({\n *   metricName: 'page_load_time',\n *   value: 1250,\n *   flagStates: { 'new-checkout': true, 'lazy-load': true },\n * });\n * ```\n */\n\nimport {\n  createContext,\n  useContext,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n  type ReactNode,\n} from 'react';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Flag exposure event\n */\nexport interface FlagExposureEvent {\n  /** Feature flag key */\n  readonly flagKey: string;\n  /** Variant shown */\n  readonly variant: string;\n  /** User identifier */\n  readonly userId?: string;\n  /** Session identifier */\n  readonly sessionId?: string;\n  /** Page/route where exposed */\n  readonly page?: string;\n  /** Component that rendered the flag */\n  readonly component?: string;\n  /** Timestamp of exposure */\n  readonly timestamp: number;\n  /** Additional context */\n  readonly context?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Flag evaluation event\n */\nexport interface FlagEvaluationEvent {\n  /** Feature flag key */\n  readonly flagKey: string;\n  /** Evaluated value */\n  readonly value: boolean | string | number;\n  /** Reason for the value */\n  readonly reason: 'flag' | 'override' | 'default' | 'error';\n  /** Evaluation duration (ms) */\n  readonly durationMs?: number;\n  /** User identifier */\n  readonly userId?: string;\n  /** Timestamp of evaluation */\n  readonly timestamp: number;\n  /** Additional context */\n  readonly context?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Performance metric with flag correlation\n */\nexport interface CorrelatedMetric {\n  /** Metric name */\n  readonly metricName: string;\n  /** Metric value */\n  readonly value: number;\n  /** Unit of measurement */\n  readonly unit?: string;\n  /** Active flag states at time of measurement */\n  readonly flagStates: Readonly<Record<string, boolean>>;\n  /** Page/route where measured */\n  readonly page?: string;\n  /** User identifier */\n  readonly userId?: string;\n  /** Timestamp of measurement */\n  readonly timestamp: number;\n  /** Additional context */\n  readonly context?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Error event with flag context\n */\nexport interface CorrelatedError {\n  /** Error message */\n  readonly message: string;\n  /** Error stack trace */\n  readonly stack?: string;\n  /** Error type/name */\n  readonly type: string;\n  /** Active flag states when error occurred */\n  readonly flagStates: Readonly<Record<string, boolean>>;\n  /** Component where error occurred */\n  readonly component?: string;\n  /** Page/route where error occurred */\n  readonly page?: string;\n  /** Timestamp of error */\n  readonly timestamp: number;\n  /** Additional context */\n  readonly context?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Feature impact metric\n */\nexport interface FeatureImpactMetric {\n  /** Feature flag key */\n  readonly flagKey: string;\n  /** Metric name */\n  readonly metricName: string;\n  /** Value when flag is enabled */\n  readonly enabledValue: number;\n  /** Value when flag is disabled */\n  readonly disabledValue: number;\n  /** Percentage change */\n  readonly percentageChange: number;\n  /** Sample size for enabled variant */\n  readonly enabledSampleSize: number;\n  /** Sample size for disabled variant */\n  readonly disabledSampleSize: number;\n  /** Statistical significance (p-value) */\n  readonly pValue?: number;\n  /** Confidence interval */\n  readonly confidenceInterval?: {\n    lower: number;\n    upper: number;\n  };\n  /** Time period of analysis */\n  readonly period: {\n    start: number;\n    end: number;\n  };\n}\n\n/**\n * Analytics destinations\n */\nexport interface AnalyticsDestination {\n  /** Destination identifier */\n  readonly id: string;\n  /** Whether destination is enabled */\n  readonly enabled: boolean;\n  /** Send exposure event */\n  sendExposure?(event: FlagExposureEvent): void | Promise<void>;\n  /** Send evaluation event */\n  sendEvaluation?(event: FlagEvaluationEvent): void | Promise<void>;\n  /** Send correlated metric */\n  sendMetric?(metric: CorrelatedMetric): void | Promise<void>;\n  /** Send correlated error */\n  sendError?(error: CorrelatedError): void | Promise<void>;\n  /** Batch send events */\n  flush?(): void | Promise<void>;\n}\n\n/**\n * Bridge configuration\n */\nexport interface FlagAnalyticsBridgeConfig {\n  /** Destinations for analytics */\n  readonly destinations?: readonly AnalyticsDestination[];\n  /** Callback for exposure events */\n  readonly onExposure?: (event: FlagExposureEvent) => void;\n  /** Callback for evaluation events */\n  readonly onEvaluation?: (event: FlagEvaluationEvent) => void;\n  /** Callback for correlated metrics */\n  readonly onMetric?: (metric: CorrelatedMetric) => void;\n  /** Callback for correlated errors */\n  readonly onError?: (error: CorrelatedError) => void;\n  /** Callback for impact metrics */\n  readonly onImpact?: (metric: FeatureImpactMetric) => void;\n  /** Enable batching of events */\n  readonly batchEnabled?: boolean;\n  /** Batch size before flushing */\n  readonly batchSize?: number;\n  /** Batch flush interval (ms) */\n  readonly batchInterval?: number;\n  /** Sample rate for events (0-1) */\n  readonly sampleRate?: number;\n  /** Debug mode */\n  readonly debug?: boolean;\n  /** Get current user ID */\n  readonly getUserId?: () => string | undefined;\n  /** Get current session ID */\n  readonly getSessionId?: () => string | undefined;\n  /** Get current page/route */\n  readonly getPage?: () => string | undefined;\n  /** Get current flags */\n  readonly getFlags?: () => Record<string, boolean>;\n}\n\n/**\n * Bridge interface\n */\nexport interface FlagAnalyticsBridge {\n  /** Track flag exposure */\n  trackExposure(event: Omit<FlagExposureEvent, 'timestamp'>): void;\n  /** Track flag evaluation */\n  trackEvaluation(event: Omit<FlagEvaluationEvent, 'timestamp'>): void;\n  /** Track correlated performance metric */\n  trackPerformanceCorrelation(metric: Omit<CorrelatedMetric, 'timestamp' | 'flagStates'>): void;\n  /** Track correlated error */\n  trackCorrelatedError(error: Omit<CorrelatedError, 'timestamp' | 'flagStates'>): void;\n  /** Record impact metric */\n  recordImpactMetric(metric: FeatureImpactMetric): void;\n  /** Add analytics destination */\n  addDestination(destination: AnalyticsDestination): void;\n  /** Remove analytics destination */\n  removeDestination(destinationId: string): void;\n  /** Flush pending events */\n  flush(): Promise<void>;\n  /** Get exposure count for a flag */\n  getExposureCount(flagKey: string): number;\n  /** Get evaluation count for a flag */\n  getEvaluationCount(flagKey: string): number;\n  /** Get all tracked metrics */\n  getMetrics(): CorrelatedMetric[];\n  /** Get impact summary for a flag */\n  getImpactSummary(flagKey: string): FeatureImpactMetric[];\n  /** Reset all tracking data */\n  reset(): void;\n  /** Enable/disable the bridge */\n  setEnabled(enabled: boolean): void;\n  /** Check if bridge is enabled */\n  isEnabled(): boolean;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Create an analytics bridge\n */\nexport function createAnalyticsBridge(config: FlagAnalyticsBridgeConfig = {}): FlagAnalyticsBridge {\n  const {\n    destinations = [],\n    onExposure,\n    onEvaluation,\n    onMetric,\n    onError,\n    onImpact,\n    batchEnabled = true,\n    batchSize = 50,\n    batchInterval = 5000,\n    sampleRate = 1,\n    debug = false,\n    getUserId,\n    getSessionId,\n    getPage,\n    getFlags,\n  } = config;\n\n  // State\n  let enabled = true;\n  const registeredDestinations = new Map<string, AnalyticsDestination>();\n  const exposureCounts = new Map<string, number>();\n  const evaluationCounts = new Map<string, number>();\n  const metricsBuffer: CorrelatedMetric[] = [];\n  const impactMetrics = new Map<string, FeatureImpactMetric[]>();\n\n  // Batching\n  const exposureBatch: FlagExposureEvent[] = [];\n  const evaluationBatch: FlagEvaluationEvent[] = [];\n  let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n  // Initialize destinations\n  for (const dest of destinations) {\n    registeredDestinations.set(dest.id, dest);\n  }\n\n  const log = (...args: unknown[]): void => {\n    if (debug) {\n      console.info('[FlagAnalyticsBridge]', ...args);\n    }\n  };\n\n  const shouldSample = (): boolean => {\n    return Math.random() < sampleRate;\n  };\n\n  const scheduleFlush = (): void => {\n    if (!batchEnabled || flushTimer) return;\n\n    flushTimer = setTimeout(() => {\n      flushTimer = null;\n      void bridge.flush();\n    }, batchInterval);\n  };\n\n  const sendToDestinations = async <T,>(\n    eventType: 'exposure' | 'evaluation' | 'metric' | 'error',\n    event: T\n  ): Promise<void> => {\n    const promises: Promise<void>[] = [];\n\n    for (const dest of registeredDestinations.values()) {\n      if (!dest.enabled) continue;\n\n      try {\n        let promise: void | Promise<void>;\n\n        switch (eventType) {\n          case 'exposure':\n            promise = dest.sendExposure?.(event as FlagExposureEvent);\n            break;\n          case 'evaluation':\n            promise = dest.sendEvaluation?.(event as FlagEvaluationEvent);\n            break;\n          case 'metric':\n            promise = dest.sendMetric?.(event as CorrelatedMetric);\n            break;\n          case 'error':\n            promise = dest.sendError?.(event as CorrelatedError);\n            break;\n        }\n\n        if (promise instanceof Promise) {\n          promises.push(promise);\n        }\n      } catch (err) {\n        log(`Error sending ${eventType} to ${dest.id}:`, err);\n      }\n    }\n\n    await Promise.allSettled(promises);\n  };\n\n  const bridge: FlagAnalyticsBridge = {\n    trackExposure(event) {\n      if (!enabled || !shouldSample()) return;\n\n      const fullEvent: FlagExposureEvent = {\n        ...event,\n        userId: event.userId ?? getUserId?.(),\n        sessionId: event.sessionId ?? getSessionId?.(),\n        page: event.page ?? getPage?.(),\n        timestamp: Date.now(),\n      };\n\n      log('Exposure:', fullEvent);\n\n      // Update counts\n      exposureCounts.set(event.flagKey, (exposureCounts.get(event.flagKey) ?? 0) + 1);\n\n      // Callback\n      onExposure?.(fullEvent);\n\n      if (batchEnabled) {\n        exposureBatch.push(fullEvent);\n        if (exposureBatch.length >= batchSize) {\n          void this.flush();\n        } else {\n          scheduleFlush();\n        }\n      } else {\n        void sendToDestinations('exposure', fullEvent);\n      }\n    },\n\n    trackEvaluation(event) {\n      if (!enabled || !shouldSample()) return;\n\n      const fullEvent: FlagEvaluationEvent = {\n        ...event,\n        userId: event.userId ?? getUserId?.(),\n        timestamp: Date.now(),\n      };\n\n      log('Evaluation:', fullEvent);\n\n      // Update counts\n      evaluationCounts.set(event.flagKey, (evaluationCounts.get(event.flagKey) ?? 0) + 1);\n\n      // Callback\n      onEvaluation?.(fullEvent);\n\n      if (batchEnabled) {\n        evaluationBatch.push(fullEvent);\n        if (evaluationBatch.length >= batchSize) {\n          void this.flush();\n        } else {\n          scheduleFlush();\n        }\n      } else {\n        void sendToDestinations('evaluation', fullEvent);\n      }\n    },\n\n    trackPerformanceCorrelation(metric) {\n      if (!enabled || !shouldSample()) return;\n\n      const fullMetric: CorrelatedMetric = {\n        ...metric,\n        flagStates: getFlags?.() ?? {},\n        userId: metric.userId ?? getUserId?.(),\n        page: metric.page ?? getPage?.(),\n        timestamp: Date.now(),\n      };\n\n      log('Performance Correlation:', fullMetric);\n\n      metricsBuffer.push(fullMetric);\n\n      // Callback\n      onMetric?.(fullMetric);\n\n      void sendToDestinations('metric', fullMetric);\n    },\n\n    trackCorrelatedError(error) {\n      if (!enabled) return;\n\n      const fullError: CorrelatedError = {\n        ...error,\n        flagStates: getFlags?.() ?? {},\n        page: error.page ?? getPage?.(),\n        timestamp: Date.now(),\n      };\n\n      log('Correlated Error:', fullError);\n\n      // Callback\n      onError?.(fullError);\n\n      void sendToDestinations('error', fullError);\n    },\n\n    recordImpactMetric(metric) {\n      const existing = impactMetrics.get(metric.flagKey) ?? [];\n      existing.push(metric);\n      impactMetrics.set(metric.flagKey, existing);\n\n      log('Impact Metric:', metric);\n\n      onImpact?.(metric);\n    },\n\n    addDestination(destination) {\n      registeredDestinations.set(destination.id, destination);\n      log('Added destination:', destination.id);\n    },\n\n    removeDestination(destinationId) {\n      registeredDestinations.delete(destinationId);\n      log('Removed destination:', destinationId);\n    },\n\n    async flush(): Promise<void> {\n      if (flushTimer) {\n        clearTimeout(flushTimer);\n        flushTimer = null;\n      }\n\n      const exposures = [...exposureBatch];\n      const evaluations = [...evaluationBatch];\n\n      exposureBatch.length = 0;\n      evaluationBatch.length = 0;\n\n      log(`Flushing ${exposures.length} exposures, ${evaluations.length} evaluations`);\n\n      const promises: Promise<void>[] = [];\n\n      for (const exposure of exposures) {\n        promises.push(sendToDestinations('exposure', exposure));\n      }\n\n      for (const evaluation of evaluations) {\n        promises.push(sendToDestinations('evaluation', evaluation));\n      }\n\n      // Also flush destinations\n      for (const dest of registeredDestinations.values()) {\n        if (dest.enabled && dest.flush) {\n          const result = dest.flush();\n          if (result instanceof Promise) {\n            promises.push(result);\n          }\n        }\n      }\n\n      await Promise.allSettled(promises);\n    },\n\n    getExposureCount(flagKey: string): number {\n      return exposureCounts.get(flagKey) ?? 0;\n    },\n\n    getEvaluationCount(flagKey: string): number {\n      return evaluationCounts.get(flagKey) ?? 0;\n    },\n\n    getMetrics(): CorrelatedMetric[] {\n      return [...metricsBuffer];\n    },\n\n    getImpactSummary(flagKey: string): FeatureImpactMetric[] {\n      return impactMetrics.get(flagKey) ?? [];\n    },\n\n    reset(): void {\n      exposureCounts.clear();\n      evaluationCounts.clear();\n      metricsBuffer.length = 0;\n      impactMetrics.clear();\n      exposureBatch.length = 0;\n      evaluationBatch.length = 0;\n\n      if (flushTimer) {\n        clearTimeout(flushTimer);\n        flushTimer = null;\n      }\n\n      log('Reset');\n    },\n\n    setEnabled(value: boolean): void {\n      enabled = value;\n      log('Enabled:', enabled);\n    },\n\n    isEnabled(): boolean {\n      return enabled;\n    },\n  };\n\n  return bridge;\n}\n\n// =============================================================================\n// Global Bridge\n// =============================================================================\n\nlet globalBridge: FlagAnalyticsBridge | null = null;\n\n/**\n * Get the global analytics bridge\n */\nexport function getAnalyticsBridge(): FlagAnalyticsBridge {\n  globalBridge ??= createAnalyticsBridge();\n  return globalBridge;\n}\n\n/**\n * Initialize the global analytics bridge\n */\nexport function initAnalyticsBridge(config: FlagAnalyticsBridgeConfig): FlagAnalyticsBridge {\n  globalBridge = createAnalyticsBridge(config);\n  return globalBridge;\n}\n\n/**\n * Reset the global analytics bridge\n */\nexport function resetAnalyticsBridge(): void {\n  globalBridge?.reset();\n  globalBridge = null;\n}\n\n// =============================================================================\n// React Integration\n// =============================================================================\n\n/**\n * Context value for flag analytics\n */\nexport interface FlagAnalyticsContextValue {\n  /** Track exposure */\n  trackExposure(flagKey: string, variant: string, context?: Record<string, unknown>): void;\n  /** Track evaluation */\n  trackEvaluation(\n    flagKey: string,\n    value: boolean | string | number,\n    reason: 'flag' | 'override' | 'default' | 'error'\n  ): void;\n  /** Track performance metric */\n  trackMetric(metricName: string, value: number, unit?: string): void;\n  /** Track error */\n  trackError(error: Error, component?: string): void;\n  /** Get exposure count */\n  getExposureCount(flagKey: string): number;\n  /** Flush analytics */\n  flush(): Promise<void>;\n}\n\nconst FlagAnalyticsContext = createContext<FlagAnalyticsContextValue | null>(null);\n\n/**\n * Props for FlagAnalyticsProvider\n */\nexport interface FlagAnalyticsProviderProps {\n  readonly children: ReactNode;\n  /** Analytics bridge instance */\n  readonly bridge?: FlagAnalyticsBridge;\n  /** Bridge configuration */\n  readonly config?: FlagAnalyticsBridgeConfig;\n  /** Get current flags */\n  readonly getFlags?: () => Record<string, boolean>;\n}\n\n/**\n * Provider component for flag analytics\n */\nexport function FlagAnalyticsProvider({\n  children,\n  bridge: providedBridge,\n  config,\n  getFlags,\n}: FlagAnalyticsProviderProps): React.JSX.Element {\n  const bridge = useMemo(() => {\n    if (providedBridge) return providedBridge;\n    if (config) return createAnalyticsBridge({ ...config, getFlags });\n    return getAnalyticsBridge();\n  }, [providedBridge, config, getFlags]);\n\n  // Flush on unmount\n  useEffect(() => {\n    return () => {\n      void bridge.flush();\n    };\n  }, [bridge]);\n\n  const trackExposure = useCallback(\n    (flagKey: string, variant: string, context?: Record<string, unknown>) => {\n      bridge.trackExposure({ flagKey, variant, context });\n    },\n    [bridge]\n  );\n\n  const trackEvaluation = useCallback(\n    (\n      flagKey: string,\n      value: boolean | string | number,\n      reason: 'flag' | 'override' | 'default' | 'error'\n    ) => {\n      bridge.trackEvaluation({ flagKey, value, reason });\n    },\n    [bridge]\n  );\n\n  const trackMetric = useCallback(\n    (metricName: string, value: number, unit?: string) => {\n      bridge.trackPerformanceCorrelation({ metricName, value, unit });\n    },\n    [bridge]\n  );\n\n  const trackError = useCallback(\n    (error: Error, component?: string) => {\n      bridge.trackCorrelatedError({\n        message: error.message,\n        stack: error.stack,\n        type: error.name,\n        component,\n      });\n    },\n    [bridge]\n  );\n\n  const getExposureCount = useCallback(\n    (flagKey: string): number => {\n      return bridge.getExposureCount(flagKey);\n    },\n    [bridge]\n  );\n\n  const flush = useCallback(async () => {\n    await bridge.flush();\n  }, [bridge]);\n\n  const contextValue = useMemo<FlagAnalyticsContextValue>(\n    () => ({\n      trackExposure,\n      trackEvaluation,\n      trackMetric,\n      trackError,\n      getExposureCount,\n      flush,\n    }),\n    [trackExposure, trackEvaluation, trackMetric, trackError, getExposureCount, flush]\n  );\n\n  return (\n    <FlagAnalyticsContext.Provider value={contextValue}>{children}</FlagAnalyticsContext.Provider>\n  );\n}\n\n/**\n * Hook to access flag analytics\n */\nexport function useFlagAnalytics(): FlagAnalyticsContextValue {\n  const context = useContext(FlagAnalyticsContext);\n  if (!context) {\n    throw new Error('useFlagAnalytics must be used within a FlagAnalyticsProvider');\n  }\n  return context;\n}\n\n/**\n * Hook to automatically track exposure when a flag is used\n */\nexport function useTrackedFeatureFlag(\n  flagKey: string,\n  isEnabled: boolean,\n  options: {\n    trackOnMount?: boolean;\n    trackOnChange?: boolean;\n    context?: Record<string, unknown>;\n  } = {}\n): void {\n  const { trackOnMount = true, trackOnChange = true, context } = options;\n  const analytics = useFlagAnalytics();\n  const [prevValue, setPrevValue] = useState(isEnabled);\n\n  // Track on mount\n  useEffect(() => {\n    if (trackOnMount) {\n      analytics.trackExposure(flagKey, isEnabled ? 'enabled' : 'disabled', context);\n    }\n    // Only run on mount\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  // Track on change\n  useEffect(() => {\n    if (trackOnChange && prevValue !== isEnabled) {\n      analytics.trackExposure(flagKey, isEnabled ? 'enabled' : 'disabled', context);\n      setPrevValue(isEnabled);\n    }\n  }, [isEnabled, prevValue, flagKey, analytics, trackOnChange, context]);\n}\n\n// =============================================================================\n// Pre-built Destinations\n// =============================================================================\n\n/**\n * Create a console analytics destination (for debugging)\n */\nexport function createConsoleDestination(prefix = '[Analytics]'): AnalyticsDestination {\n  return {\n    id: 'console',\n    enabled: true,\n    sendExposure(event) {\n      console.info(prefix, 'Exposure:', event);\n    },\n    sendEvaluation(event) {\n      console.info(prefix, 'Evaluation:', event);\n    },\n    sendMetric(metric) {\n      console.info(prefix, 'Metric:', metric);\n    },\n    sendError(error) {\n      console.error(prefix, 'Error:', error);\n    },\n  };\n}\n\n/**\n * Create a localStorage analytics destination (for offline storage)\n */\nexport function createLocalStorageDestination(key = 'flag_analytics'): AnalyticsDestination {\n  let events: unknown[] = [];\n\n  try {\n    const stored = localStorage.getItem(key);\n    if (stored != null && stored !== '') {\n      events = JSON.parse(stored) as unknown[];\n    }\n  } catch {\n    events = [];\n  }\n\n  const save = (): void => {\n    try {\n      localStorage.setItem(key, JSON.stringify(events.slice(-1000)));\n    } catch {\n      // Storage full, clear old events\n      events = events.slice(-500);\n      localStorage.setItem(key, JSON.stringify(events));\n    }\n  };\n\n  return {\n    id: 'localStorage',\n    enabled: true,\n    sendExposure(event) {\n      events.push({ type: 'exposure', data: event });\n      save();\n    },\n    sendEvaluation(event) {\n      events.push({ type: 'evaluation', data: event });\n      save();\n    },\n    sendMetric(metric) {\n      events.push({ type: 'metric', data: metric });\n      save();\n    },\n    sendError(error) {\n      events.push({ type: 'error', data: error });\n      save();\n    },\n    flush() {\n      save();\n    },\n  };\n}\n\n/**\n * Create an HTTP endpoint analytics destination\n *\n * Note: This function intentionally uses raw fetch() rather than apiClient because:\n * 1. Analytics destinations should be independent of the main API client\n * 2. Analytics endpoints may be third-party services on different domains\n * 3. Avoids circular dependencies between analytics and API layers\n *\n * @see {@link @/lib/api/api-client} for application API calls\n */\nexport function createHttpDestination(config: {\n  endpoint: string;\n  method?: 'POST' | 'PUT';\n  headers?: Record<string, string>;\n  batchEvents?: boolean;\n}): AnalyticsDestination {\n  const { endpoint, method = 'POST', headers = {}, batchEvents = true } = config;\n\n  let batch: unknown[] = [];\n\n  const send = async (events: unknown[]): Promise<void> => {\n    try {\n      // Raw fetch is intentional - analytics should be independent of apiClient\n      await fetch(endpoint, {\n        method,\n        headers: {\n          'Content-Type': 'application/json',\n          ...headers,\n        },\n        body: JSON.stringify(batchEvents ? events : events[0]),\n      });\n    } catch (error) {\n      console.error('[HttpDestination] Failed to send:', error);\n    }\n  };\n\n  const queueEvent = (event: unknown): void => {\n    if (batchEvents) {\n      batch.push(event);\n    } else {\n      void send([event]);\n    }\n  };\n\n  return {\n    id: 'http',\n    enabled: true,\n    sendExposure(event) {\n      queueEvent({ type: 'exposure', data: event });\n    },\n    sendEvaluation(event) {\n      queueEvent({ type: 'evaluation', data: event });\n    },\n    sendMetric(metric) {\n      queueEvent({ type: 'metric', data: metric });\n    },\n    sendError(error) {\n      queueEvent({ type: 'error', data: error });\n    },\n    async flush() {\n      if (batch.length > 0) {\n        const toSend = [...batch];\n        batch = [];\n        await send(toSend);\n      }\n    },\n  };\n}\n"],"names":["createAnalyticsBridge","config","destinations","onExposure","onEvaluation","onMetric","onError","onImpact","batchEnabled","batchSize","batchInterval","sampleRate","debug","getUserId","getSessionId","getPage","getFlags","enabled","registeredDestinations","exposureCounts","evaluationCounts","metricsBuffer","impactMetrics","exposureBatch","evaluationBatch","flushTimer","dest","log","args","shouldSample","scheduleFlush","bridge","sendToDestinations","eventType","event","promises","promise","err","fullEvent","metric","fullMetric","error","fullError","existing","destination","destinationId","exposures","evaluations","exposure","evaluation","result","flagKey","value","globalBridge","getAnalyticsBridge","initAnalyticsBridge","resetAnalyticsBridge","FlagAnalyticsContext","createContext","FlagAnalyticsProvider","children","providedBridge","useMemo","useEffect","trackExposure","useCallback","variant","context","trackEvaluation","reason","trackMetric","metricName","unit","trackError","component","getExposureCount","flush","contextValue","useFlagAnalytics","useContext","useTrackedFeatureFlag","isEnabled","options","trackOnMount","trackOnChange","analytics","prevValue","setPrevValue","useState","createConsoleDestination","prefix","createLocalStorageDestination","key","events","stored","save","createHttpDestination","endpoint","method","headers","batchEvents","batch","send","queueEvent","toSend"],"mappings":"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"}