FullStory User Experience Analytics

FullStory examples for user session recording, heatmaps, conversion funnels, and user behavior analysis

Key Facts

Category
Developer Tools
Items
4
Format Families
sample

Sample Overview

FullStory examples for user session recording, heatmaps, conversion funnels, and user behavior analysis This sample set belongs to Developer Tools and can be used to test related workflows inside Elysia Tools.

💻 FullStory Hello World javascript

🟢 simple

Basic FullStory setup with session recording and user identification

⏱️ 10 min 🏷️ frontend, monitoring, analytics, ux
Prerequisites: JavaScript basics, HTML/CSS, Browser APIs
// FullStory Hello World - Basic User Experience Analytics
// Simple session recording and user tracking setup

// Initialize FullStory with your organization ID
window['_fs_debug'] = false
window['_fs_host'] = 'fullstory.com'
window['_fs_org'] = 'YOUR-ORG-ID'
window['_fs_namespace'] = 'FS'
;(function(m,n,e,t,l,o,g,y){
    if (e in m) {if(m.console && m.console.log) { m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');} return;}
    g=m[e]=function(a,b,s){g.q?g.q.push([a,b,s]):g._api(a,b,s);};g.q=[];
    o=n.createElement(t);o.async=1;o.src='https://'+_fs_host+'/s/fs.js';
    y=n.getElementsByTagName(t)[0];y.parentNode.insertBefore(o,y);
    g.identify=function(i,v,s){g(l,{uid:i},s);if(v)g(l,v,s)};g.setUserVars=function(v,s){g(l,v,s)};g.event=function(i,v,s){g('event',{n:i,p:v},s)};
    g.shutdown=function(){g("rec",!1)};g.restart=function(){g("rec",!0)};
    g.consent=function(a){g("consent",!a)};g.identifyAccount=function(i,v){o='account';v=v||{};v.acctId=i;g(o,v)};
    g.clearUserCookie=function(){};
})(window,document,window['_fs_namespace'],'script','user');

// Identify the current user
FS.identify('user-123', {
  displayName: 'John Doe',
  email: '[email protected]',
  plan: 'premium',
  signUpDate: '2023-01-15',
  lastLogin: new Date().toISOString()
});

// Track custom events
FS.event('User signed up', {
  method: 'email',
  referralSource: 'google',
  userAgent: navigator.userAgent
});

// Track page views
FS.event('Page Viewed', {
  page: window.location.pathname,
  title: document.title,
  referrer: document.referrer
});

console.log('FullStory initialized and recording!');

// Track form submissions
document.addEventListener('submit', function(event) {
  const form = event.target;
  if (form.tagName === 'FORM') {
    FS.event('Form Submitted', {
      formId: form.id,
      formName: form.name,
      action: form.action
    });
  }
});

// Track clicks on important elements
document.addEventListener('click', function(event) {
  const element = event.target;
  if (element.matches('button, a, input[type="button"], input[type="submit"]')) {
    FS.event('Button Clicked', {
      text: element.textContent || element.value,
      className: element.className,
      id: element.id
    });
  }
});

💻 E-commerce Conversion Funnel typescript

🟡 intermediate ⭐⭐⭐

Track user journey through e-commerce checkout process with conversion analytics

⏱️ 30 min 🏷️ frontend, analytics, e-commerce, conversion
Prerequisites: JavaScript/TypeScript, E-commerce concepts, Event tracking, FullStory API
// FullStory E-commerce Conversion Funnel Analysis
// Track user journey through checkout process and identify drop-off points

interface ConversionEvent {
  step: string
  userId: string
  sessionId: string
  timestamp: number
  properties: Record<string, any>
}

interface FunnelStep {
  name: string
  stepOrder: number
  eventTrigger: string
  properties: string[]
}

class EcommerceFunnelTracker {
  private funnelSteps: FunnelStep[] = [
    {
      name: 'Product View',
      stepOrder: 1,
      eventTrigger: 'Product Viewed',
      properties: ['productId', 'productName', 'price', 'category']
    },
    {
      name: 'Add to Cart',
      stepOrder: 2,
      eventTrigger: 'Product Added to Cart',
      properties: ['productId', 'quantity', 'price']
    },
    {
      name: 'View Cart',
      stepOrder: 3,
      eventTrigger: 'Cart Viewed',
      properties: ['cartValue', 'itemCount']
    },
    {
      name: 'Checkout Started',
      stepOrder: 4,
      eventTrigger: 'Checkout Started',
      properties: ['cartValue', 'itemCount', 'paymentMethod']
    },
    {
      name: 'Payment Info',
      stepOrder: 5,
      eventTrigger: 'Payment Info Entered',
      properties: ['paymentMethod', 'billingAddress']
    },
    {
      name: 'Order Completed',
      stepOrder: 6,
      eventTrigger: 'Order Completed',
      properties: ['orderId', 'totalAmount', 'items']
    }
  ]

  private currentStep: number = 0
  private userJourney: ConversionEvent[] = []

  constructor() {
    this.initializeTracking()
  }

  private initializeTracking() {
    // Track product views
    this.trackProductViews()

    // Track cart interactions
    this.trackCartInteractions()

    // Track checkout process
    this.trackCheckoutProcess()

    // Track order completion
    this.trackOrderCompletion()
  }

  private trackProductViews() {
    // Track when product pages are viewed
    const trackProductView = (productId: string, productName: string, price: number, category: string) => {
      this.recordFunnelEvent('Product Viewed', {
        productId,
        productName,
        price,
        category,
        timestamp: Date.now()
      })
    }

    // Example: Call this when product page loads
    // trackProductView('prod-123', 'Wireless Headphones', 99.99, 'Electronics')
  }

  private trackCartInteractions() {
    // Track add to cart events
    window.addEventListener('click', (event) => {
      const target = event.target as HTMLElement
      if (target.matches('[data-add-to-cart]')) {
        const productId = target.getAttribute('data-product-id')
        const productName = target.getAttribute('data-product-name')
        const price = parseFloat(target.getAttribute('data-price') || '0')
        const quantity = parseInt(target.getAttribute('data-quantity') || '1')

        this.recordFunnelEvent('Product Added to Cart', {
          productId,
          productName,
          price,
          quantity,
          timestamp: Date.now()
        })
      }
    })

    // Track cart view
    const trackCartView = () => {
      const cartItems = this.getCartItems()
      const cartValue = cartItems.reduce((total, item) => total + (item.price * item.quantity), 0)

      this.recordFunnelEvent('Cart Viewed', {
        cartValue,
        itemCount: cartItems.length,
        items: cartItems,
        timestamp: Date.now()
      })
    }

    // Call trackCartView() when cart page is visited
  }

  private trackCheckoutProcess() {
    // Track checkout start
    const trackCheckoutStart = () => {
      const cartItems = this.getCartItems()
      const cartValue = cartItems.reduce((total, item) => total + (item.price * item.quantity), 0)

      this.recordFunnelEvent('Checkout Started', {
        cartValue,
        itemCount: cartItems.length,
        items: cartItems,
        timestamp: Date.now()
      })
    }

    // Track payment method selection
    const trackPaymentMethod = (paymentMethod: string) => {
      this.recordFunnelEvent('Payment Method Selected', {
        paymentMethod,
        timestamp: Date.now()
      })
    }

    // Track shipping info entered
    const trackShippingInfo = (shippingData: any) => {
      this.recordFunnelEvent('Shipping Info Entered', {
        shippingMethod: shippingData.method,
        shippingAddress: shippingData.address,
        timestamp: Date.now()
      })
    }

    // Track payment info entered
    const trackPaymentInfo = (paymentMethod: string) => {
      this.recordFunnelEvent('Payment Info Entered', {
        paymentMethod,
        timestamp: Date.now()
      })
    }
  }

  private trackOrderCompletion() {
    const trackOrderCompletion = (orderData: {
      orderId: string
      totalAmount: number
      items: any[]
      paymentMethod: string
      shippingMethod: string
    }) => {
      this.recordFunnelEvent('Order Completed', {
        orderId: orderData.orderId,
        totalAmount: orderData.totalAmount,
        items: orderData.items,
        paymentMethod: orderData.paymentMethod,
        shippingMethod: orderData.shippingMethod,
        timestamp: Date.now()
      })

      // Track conversion value
      this.recordConversionValue(orderData.totalAmount)
    }
  }

  private recordFunnelEvent(eventName: string, properties: Record<string, any>) {
    // Send to FullStory
    FS.event(eventName, properties)

    // Record in internal journey
    const event: ConversionEvent = {
      step: eventName,
      userId: this.getCurrentUserId(),
      sessionId: this.getCurrentSessionId(),
      timestamp: properties.timestamp,
      properties
    }

    this.userJourney.push(event)
    this.updateCurrentStep(eventName)

    console.log('Funnel event recorded:', event)
  }

  private updateCurrentStep(eventName: string) {
    const stepIndex = this.funnelSteps.findIndex(step => step.eventTrigger === eventName)
    if (stepIndex !== -1 && stepIndex > this.currentStep) {
      this.currentStep = stepIndex
    }
  }

  private recordConversionValue(value: number) {
    FS.event('Conversion Value Recorded', {
      value,
      currency: 'USD',
      timestamp: Date.now()
    })
  }

  private getCartItems() {
    // Implement cart retrieval logic
    // This would typically get cart data from your state management or API
    return [
      {
        productId: 'prod-123',
        productName: 'Wireless Headphones',
        price: 99.99,
        quantity: 1
      }
    ]
  }

  private getCurrentUserId(): string {
    // Get current user ID from your authentication system
    return 'user-123'
  }

  private getCurrentSessionId(): string {
    // FullStory provides session ID
    return (window as any).FS?.getCurrentSession?.()?.session?.id || 'unknown'
  }

  // Generate funnel analysis
  public generateFunnelReport() {
    const funnelData = this.analyzeFunnelPerformance()

    FS.event('Funnel Analysis Generated', {
      totalSteps: this.funnelSteps.length,
      completionRate: funnelData.completionRate,
      dropOffPoints: funnelData.dropOffPoints,
      averageTime: funnelData.averageTime,
      timestamp: Date.now()
    })

    return funnelData
  }

  private analyzeFunnelPerformance() {
    const stepCounts: Record<string, number> = {}
    const stepTimes: Record<string, number[]> = {}

    // Count events per step and calculate times
    this.funnelSteps.forEach(step => {
      const stepEvents = this.userJourney.filter(event => event.step === step.eventTrigger)
      stepCounts[step.name] = stepEvents.length

      // Calculate time between steps
      if (step.stepOrder > 1) {
        const previousStep = this.funnelSteps.find(s => s.stepOrder === step.stepOrder - 1)
        if (previousStep) {
          const timeDiffs: number[] = []

          stepEvents.forEach(currentEvent => {
            const previousEvent = this.userJourney.find(
              event => event.step === previousStep.eventTrigger &&
                       event.userId === currentEvent.userId &&
                       event.timestamp < currentEvent.timestamp
            )

            if (previousEvent) {
              timeDiffs.push(currentEvent.timestamp - previousEvent.timestamp)
            }
          })

          stepTimes[step.name] = timeDiffs
        }
      }
    })

    // Calculate drop-off points
    const dropOffPoints: string[] = []
    for (let i = 0; i < this.funnelSteps.length - 1; i++) {
      const currentStepCount = stepCounts[this.funnelSteps[i].name] || 0
      const nextStepCount = stepCounts[this.funnelSteps[i + 1].name] || 0

      if (nextStepCount < currentStepCount) {
        const dropOffRate = ((currentStepCount - nextStepCount) / currentStepCount) * 100
        dropOffPoints.push(`${this.funnelSteps[i].name} → ${this.funnelSteps[i + 1].name}: ${dropOffRate.toFixed(1)}%`)
      }
    }

    // Calculate overall completion rate
    const firstStepCount = stepCounts[this.funnelSteps[0].name] || 1
    const lastStepCount = stepCounts[this.funnelSteps[this.funnelSteps.length - 1].name] || 0
    const completionRate = (lastStepCount / firstStepCount) * 100

    // Calculate average time per step
    const averageTimes: Record<string, number> = {}
    Object.entries(stepTimes).forEach(([stepName, times]) => {
      if (times.length > 0) {
        averageTimes[stepName] = times.reduce((sum, time) => sum + time, 0) / times.length
      }
    })

    return {
      stepCounts,
      dropOffPoints,
      completionRate,
      averageTime: averageTimes,
      totalUsers: this.userJourney.filter((event, index, arr) =>
        arr.findIndex(e => e.userId === event.userId) === index
      ).length
    }
  }

  // Track user abandonment
  public trackAbandonment() {
    const abandonEvent = () => {
      const currentStepName = this.funnelSteps[this.currentStep]?.name || 'Unknown'

      FS.event('Funnel Abandoned', {
        currentStep: currentStepName,
        stepOrder: this.currentStep + 1,
        totalSteps: this.funnelSteps.length,
        journeyLength: this.userJourney.length,
        timestamp: Date.now()
      })
    }

    // Track abandonment on page unload
    window.addEventListener('beforeunload', abandonEvent)
  }
}

// Initialize the funnel tracker
const funnelTracker = new EcommerceFunnelTracker()
funnelTracker.trackAbandonment()

// Example usage
// funnelTracker.trackProductView('prod-123', 'Wireless Headphones', 99.99, 'Electronics')
// funnelTracker.generateFunnelReport()

export { EcommerceFunnelTracker, funnelTracker }

💻 React Integration with Hooks tsx

🟡 intermediate ⭐⭐⭐

Modern React integration with hooks for FullStory tracking and user behavior analysis

⏱️ 35 min 🏷️ react, frontend, hooks, analytics, user-experience
Prerequisites: React hooks, TypeScript, FullStory API, Event tracking
// FullStory React Integration with Hooks
// Modern React hooks for comprehensive user experience tracking

import React, { useEffect, useCallback, useState, useRef } from 'react'

// Type definitions
interface UserTraits {
  displayName?: string
  email?: string
  plan?: string
  signUpDate?: string
  [key: string]: any
}

interface EventProperties {
  [key: string]: any
}

// Custom hook for FullStory user identification
export const useFullStoryUser = (userId: string, traits: UserTraits = {}) => {
  useEffect(() => {
    if (typeof window !== 'undefined' && window.FS) {
      window.FS.identify(userId, traits)
    }
  }, [userId, traits])
}

// Custom hook for FullStory event tracking
export const useFullStoryEvent = () => {
  const trackEvent = useCallback((eventName: string, properties: EventProperties = {}) => {
    if (typeof window !== 'undefined' && window.FS) {
      window.FS.event(eventName, {
        ...properties,
        timestamp: Date.now(),
        url: window.location.href,
        userAgent: navigator.userAgent
      })
    }
  }, [])

  return { trackEvent }
}

// Custom hook for tracking page views
export const useFullStoryPageView = (pageName?: string) => {
  const { trackEvent } = useFullStoryEvent()

  useEffect(() => {
    const pageNameToUse = pageName || window.location.pathname
    trackEvent('Page Viewed', {
      page: pageNameToUse,
      title: document.title,
      referrer: document.referrer
    })
  }, [trackEvent, pageName])
}

// Custom hook for tracking form interactions
export const useFullStoryFormTracking = (formId: string) => {
  const { trackEvent } = useFullStoryEvent()
  const [formData, setFormData] = useState<Record<string, any>>({})

  const trackFieldFocus = useCallback((fieldName: string, fieldType: string) => {
    trackEvent('Form Field Focused', {
      formId,
      fieldName,
      fieldType,
      timestamp: Date.now()
    })
  }, [formId, trackEvent])

  const trackFieldBlur = useCallback((fieldName: string, value: string) => {
    setFormData(prev => ({ ...prev, [fieldName]: value }))
    trackEvent('Form Field Blurred', {
      formId,
      fieldName,
      hasValue: value.length > 0,
      timestamp: Date.now()
    })
  }, [formId, trackEvent])

  const trackFormSubmit = useCallback((data: Record<string, any>) => {
    trackEvent('Form Submitted', {
      formId,
      fieldCount: Object.keys(data).length,
      hasValidationErrors: Object.values(data).some(val => !val),
      timestamp: Date.now()
    })
  }, [formId, trackEvent])

  const trackValidationError = useCallback((fieldName: string, errorMessage: string) => {
    trackEvent('Form Validation Error', {
      formId,
      fieldName,
      errorMessage,
      timestamp: Date.now()
    })
  }, [formId, trackEvent])

  return {
    formData,
    trackFieldFocus,
    trackFieldBlur,
    trackFormSubmit,
    trackValidationError
  }
}

// Custom hook for tracking user engagement
export const useFullStoryEngagement = () => {
  const { trackEvent } = useFullStoryEvent()
  const [sessionStartTime] = useState(Date.now())
  const [lastActivityTime, setLastActivityTime] = useState(Date.now())
  const [totalActiveTime, setTotalActiveTime] = useState(0)

  const trackClick = useCallback((element: HTMLElement, x: number, y: number) => {
    trackEvent('Element Clicked', {
      tagName: element.tagName,
      className: element.className,
      id: element.id,
      textContent: element.textContent?.slice(0, 50),
      x,
      y,
      timestamp: Date.now()
    })
  }, [trackEvent])

  const trackScroll = useCallback((scrollPercentage: number, direction: 'up' | 'down') => {
    trackEvent('Page Scrolled', {
      scrollPercentage,
      direction,
      timestamp: Date.now()
    })
  }, [trackEvent])

  const trackEngagementEnd = useCallback(() => {
    const sessionDuration = Date.now() - sessionStartTime
    trackEvent('Session Ended', {
      totalDuration: sessionDuration,
      activeTime: totalActiveTime,
      lastActivity: Date.now() - lastActivityTime,
      timestamp: Date.now()
    })
  }, [sessionStartTime, totalActiveTime, lastActivityTime, trackEvent])

  // Update activity tracking
  useEffect(() => {
    const updateActivity = () => {
      const now = Date.now()
      const inactiveTime = now - lastActivityTime

      if (inactiveTime < 30000) { // Consider active if less than 30 seconds inactive
        setTotalActiveTime(prev => prev + Math.min(1000, inactiveTime))
      }

      setLastActivityTime(now)
    }

    // Track user interactions
    const events = ['click', 'scroll', 'keydown', 'mousemove']
    events.forEach(event => {
      document.addEventListener(event, updateActivity, { passive: true })
    })

    // Track session end
    window.addEventListener('beforeunload', trackEngagementEnd)

    return () => {
      events.forEach(event => {
        document.removeEventListener(event, updateActivity)
      })
      window.removeEventListener('beforeunload', trackEngagementEnd)
    }
  }, [lastActivityTime, trackEngagementEnd])

  return {
    trackClick,
    trackScroll,
    trackEngagementEnd
  }
}

// Custom hook for A/B testing integration
export const useFullStoryABTesting = (experimentName: string, variant?: string) => {
  const { trackEvent } = useFullStoryEvent()

  useEffect(() => {
    if (variant) {
      trackEvent('A/B Test Started', {
        experimentName,
        variant,
        timestamp: Date.now()
      })

      // Set user properties for segmentation
      if (window.FS) {
        window.FS.setUserVars({
          [`ab_${experimentName}`]: variant
        })
      }
    }
  }, [experimentName, variant, trackEvent])

  const trackConversion = useCallback((conversionType: string, value?: number) => {
    trackEvent('A/B Test Conversion', {
      experimentName,
      variant,
      conversionType,
      value,
      timestamp: Date.now()
    })
  }, [experimentName, variant, trackEvent])

  return { trackConversion }
}

// Higher-order component for automatic tracking
export const withFullStoryTracking = <P extends object>(
  WrappedComponent: React.ComponentType<P>,
  options: {
    trackPageView?: boolean
    trackClicks?: boolean
    componentName?: string
  } = {}
) => {
  const HOC = (props: P) => {
    const { trackEvent } = useFullStoryEvent()
    const { trackClick } = useFullStoryEngagement()
    const elementRef = useRef<HTMLDivElement>(null)

    // Track page view
    useFullStoryPageView(options.componentName)

    // Track clicks on component
    useEffect(() => {
      if (options.trackClicks && elementRef.current) {
        const handleClick = (event: MouseEvent) => {
          const target = event.target as HTMLElement
          trackClick(target, event.clientX, event.clientY)
        }

        const element = elementRef.current
        element.addEventListener('click', handleClick)

        return () => {
          element.removeEventListener('click', handleClick)
        }
      }
    }, [trackClick])

    return (
      <div ref={elementRef}>
        <WrappedComponent {...props} />
      </div>
    )
  }

  HOC.displayName = `withFullStoryTracking(${WrappedComponent.displayName || WrappedComponent.name})`
  return HOC
}

// Example usage components

// User identification component
const UserProfile: React.FC<{ userId: string; name: string; email: string }> = ({ userId, name, email }) => {
  useFullStoryUser(userId, {
    displayName: name,
    email,
    plan: 'premium',
    lastLogin: new Date().toISOString()
  })

  return <div>Welcome, {name}!</div>
}

// Contact form with tracking
const ContactForm: React.FC = () => {
  const { formData, trackFieldFocus, trackFieldBlur, trackFormSubmit, trackValidationError } = useFullStoryFormTracking('contact-form')
  const { trackEvent } = useFullStoryEvent()

  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault()

    // Validate form
    if (!formData.name || !formData.email) {
      trackValidationError('name', !formData.name ? 'Name is required' : '')
      trackValidationError('email', !formData.email ? 'Email is required' : '')
      return
    }

    trackFormSubmit(formData)
    trackEvent('Contact Form Submitted', {
      name: formData.name,
      email: formData.email,
      message: formData.message?.length > 0
    })

    // Handle form submission...
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Name"
        onFocus={() => trackFieldFocus('name', 'text')}
        onBlur={(e) => trackFieldBlur('name', e.target.value)}
      />
      <input
        type="email"
        placeholder="Email"
        onFocus={() => trackFieldFocus('email', 'email')}
        onBlur={(e) => trackFieldBlur('email', e.target.value)}
      />
      <textarea
        placeholder="Message"
        onFocus={() => trackFieldFocus('message', 'textarea')}
        onBlur={(e) => trackFieldBlur('message', e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  )
}

// A/B testing example
const CheckoutButton: React.FC<{ variant: 'A' | 'B' }> = ({ variant }) => {
  const { trackConversion } = useFullStoryABTesting('checkout_button_test', variant)

  const handleClick = () => {
    trackConversion('checkout_started', 1)
    // Handle checkout logic...
  }

  return (
    <button
      onClick={handleClick}
      style={{
        backgroundColor: variant === 'A' ? '#007bff' : '#28a745',
        color: 'white',
        padding: '12px 24px',
        border: 'none',
        borderRadius: '4px',
        cursor: 'pointer'
      }}
    >
      {variant === 'A' ? 'Buy Now' : 'Proceed to Checkout'}
    </button>
  )
}

// Wrap a component with tracking
const TrackedComponent = withFullStoryTracking(
  () => <div>This component is automatically tracked</div>,
  {
    trackPageView: true,
    trackClicks: true,
    componentName: 'TrackedComponent'
  }
)

export { UserProfile, ContactForm, CheckoutButton, TrackedComponent }

💻 Advanced User Segmentation typescript

🔴 complex ⭐⭐⭐⭐⭐

Create user segments based on behavior, demographics, and engagement patterns

⏱️ 60 min 🏷️ frontend, analytics, segmentation, user-behavior
Prerequisites: TypeScript, FullStory API, Data analysis, Behavioral analytics
// FullStory Advanced User Segmentation
// Dynamic user segmentation based on behavior patterns and characteristics

interface UserSegment {
  name: string
  id: string
  criteria: SegmentCriteria
  users: string[]
  properties: SegmentProperties
}

interface SegmentCriteria {
  demographics?: DemographicCriteria
  behavior?: BehaviorCriteria
  engagement?: EngagementCriteria
  technical?: TechnicalCriteria
  custom?: CustomCriteria
}

interface DemographicCriteria {
  country?: string[]
  language?: string[]
  ageRange?: { min: number; max: number }
  plan?: string[]
  signUpDateRange?: { start: Date; end: Date }
}

interface BehaviorCriteria {
  pageViews?: {
    pages: string[]
    count?: { min: number; max?: number }
    timeframe?: number // days
  }
  events?: {
    types: string[]
    count?: { min: number; max?: number }
    timeframe?: number
  }
  conversions?: {
    types: string[]
    totalValue?: { min: number; max?: number }
    timeframe?: number
  }
}

interface EngagementCriteria {
  sessionDuration?: { min: number; max?: number }
  sessionsPerWeek?: { min: number; max?: number }
  lastActivity?: { days: number }
  scrollDepth?: { percentage: number }
  clicksPerSession?: { min: number; max?: number }
}

interface TechnicalCriteria {
  browser?: string[]
  device?: string[]
  os?: string[]
  screenResolution?: { min: { width: number; height: number }; max?: { width: number; height: number } }
  connectionSpeed?: string[]
}

interface CustomCriteria {
  [key: string]: any
}

interface SegmentProperties {
  size: number
  conversionRate: number
  averageOrderValue: number
  retentionRate: number
  churnRisk: 'low' | 'medium' | 'high'
  lifetimeValue: number
  createdAt: Date
  updatedAt: Date
}

class UserSegmentationEngine {
  private segments: Map<string, UserSegment> = new Map()
  private userProfiles: Map<string, UserProfile> = new Map()
  private eventHistory: Map<string, UserEvent[]> = new Map()

  constructor() {
    this.initializeDefaultSegments()
    this.startRealTimeSegmentation()
  }

  private initializeDefaultSegments() {
    // Power users segment
    this.createSegment({
      name: 'Power Users',
      id: 'power-users',
      criteria: {
        engagement: {
          sessionDuration: { min: 600000 }, // 10 minutes
          sessionsPerWeek: { min: 5 },
          lastActivity: { days: 7 }
        },
        behavior: {
          pageViews: {
            count: { min: 50 },
            timeframe: 30
          },
          events: {
            types: ['conversion', 'feature_used'],
            count: { min: 10 },
            timeframe: 30
          }
        }
      },
      properties: {
        size: 0,
        conversionRate: 0,
        averageOrderValue: 0,
        retentionRate: 0,
        churnRisk: 'low',
        lifetimeValue: 0,
        createdAt: new Date(),
        updatedAt: new Date()
      }
    })

    // At-risk users segment
    this.createSegment({
      name: 'At Risk Users',
      id: 'at-risk-users',
      criteria: {
        engagement: {
          sessionDuration: { max: 120000 }, // 2 minutes
          sessionsPerWeek: { max: 2 },
          lastActivity: { days: 14 }
        },
        behavior: {
          events: {
            types: ['error', 'frustration'],
            count: { min: 3 },
            timeframe: 14
          }
        }
      },
      properties: {
        size: 0,
        conversionRate: 0,
        averageOrderValue: 0,
        retentionRate: 0,
        churnRisk: 'high',
        lifetimeValue: 0,
        createdAt: new Date(),
        updatedAt: new Date()
      }
    })

    // Mobile-first users segment
    this.createSegment({
      name: 'Mobile-First Users',
      id: 'mobile-users',
      criteria: {
        technical: {
          device: ['mobile', 'tablet']
        },
        behavior: {
          pageViews: {
            count: { min: 20 },
            timeframe: 30
          }
        }
      },
      properties: {
        size: 0,
        conversionRate: 0,
        averageOrderValue: 0,
        retentionRate: 0,
        churnRisk: 'medium',
        lifetimeValue: 0,
        createdAt: new Date(),
        updatedAt: new Date()
      }
    })
  }

  createSegment(segmentData: Omit<UserSegment, 'users'>): UserSegment {
    const segment: UserSegment = {
      ...segmentData,
      users: []
    }

    this.segments.set(segment.id, segment)
    this.updateSegmentInFullStory(segment)

    FS.event('User Segment Created', {
      segmentId: segment.id,
      segmentName: segment.name,
      criteria: segment.criteria,
      timestamp: Date.now()
    })

    return segment
  }

  private updateSegmentInFullStory(segment: UserSegment) {
    // Update FullStory with segment information
    segment.users.forEach(userId => {
      const userProfile = this.userProfiles.get(userId)
      if (userProfile) {
        FS.setUserVars({
          userId,
          segments: userProfile.segments.map(segId => this.segments.get(segId)?.name).filter(Boolean)
        })
      }
    })
  }

  addUserToSegment(userId: string, segmentId: string): boolean {
    const segment = this.segments.get(segmentId)
    if (!segment) return false

    if (!segment.users.includes(userId)) {
      segment.users.push(userId)

      const userProfile = this.userProfiles.get(userId) || {
        id: userId,
        segments: [],
        properties: {},
        events: []
      }

      if (!userProfile.segments.includes(segmentId)) {
        userProfile.segments.push(segmentId)
        this.userProfiles.set(userId, userProfile)
      }

      this.updateSegmentProperties(segment)
      this.updateSegmentInFullStory(segment)

      FS.event('User Added to Segment', {
        userId,
        segmentId,
        segmentName: segment.name,
        totalUsers: segment.users.length,
        timestamp: Date.now()
      })

      return true
    }

    return false
  }

  removeUserFromSegment(userId: string, segmentId: string): boolean {
    const segment = this.segments.get(segmentId)
    if (!segment) return false

    const userIndex = segment.users.indexOf(userId)
    if (userIndex !== -1) {
      segment.users.splice(userIndex, 1)

      const userProfile = this.userProfiles.get(userId)
      if (userProfile) {
        const segmentIndex = userProfile.segments.indexOf(segmentId)
        if (segmentIndex !== -1) {
          userProfile.segments.splice(segmentIndex, 1)
        }
      }

      this.updateSegmentProperties(segment)
      this.updateSegmentInFullStory(segment)

      FS.event('User Removed from Segment', {
        userId,
        segmentId,
        segmentName: segment.name,
        totalUsers: segment.users.length,
        timestamp: Date.now()
      })

      return true
    }

    return false
  }

  private updateSegmentProperties(segment: UserSegment) {
    segment.properties.size = segment.users.length
    segment.properties.updatedAt = new Date()

    // Calculate conversion rate
    let conversions = 0
    let totalOrderValue = 0
    let activeUsers = 0

    segment.users.forEach(userId => {
      const userEvents = this.eventHistory.get(userId) || []

      // Count conversions
      const userConversions = userEvents.filter(event =>
        event.type.includes('conversion') || event.type.includes('purchase')
      ).length

      if (userConversions > 0) conversions++

      // Calculate order value
      userEvents.forEach(event => {
        if (event.properties.value) {
          totalOrderValue += event.properties.value
        }
      })

      // Check if user is active (last 30 days)
      const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000)
      if (userEvents.some(event => event.timestamp > thirtyDaysAgo)) {
        activeUsers++
      }
    })

    segment.properties.conversionRate = (conversions / segment.users.length) * 100
    segment.properties.averageOrderValue = totalOrderValue / Math.max(1, conversions)
    segment.properties.retentionRate = (activeUsers / segment.users.length) * 100

    // Calculate lifetime value (simplified)
    segment.properties.lifetimeValue = totalOrderValue / Math.max(1, segment.users.length)

    // Update churn risk based on retention
    if (segment.properties.retentionRate > 80) {
      segment.properties.churnRisk = 'low'
    } else if (segment.properties.retentionRate > 50) {
      segment.properties.churnRisk = 'medium'
    } else {
      segment.properties.churnRisk = 'high'
    }
  }

  private startRealTimeSegmentation() {
    // Listen for FullStory events
    if (typeof window !== 'undefined' && window.FS) {
      // Hook into FullStory event system
      const originalEvent = window.FS.event

      window.FS.event = (eventName: string, eventProperties: any) => {
        // Call original event
        originalEvent(eventName, eventProperties)

        // Process event for segmentation
        this.processEventForSegmentation(eventName, eventProperties)
      }
    }
  }

  private processEventForSegmentation(eventName: string, properties: any) {
    // Get user ID from event properties or session
    const userId = properties.userId || this.getCurrentUserId()

    if (!userId) return

    // Record event
    const event: UserEvent = {
      type: eventName,
      timestamp: Date.now(),
      properties
    }

    const userEvents = this.eventHistory.get(userId) || []
    userEvents.push(event)
    this.eventHistory.set(userId, userEvents)

    // Check if user should be added to any segments
    this.evaluateUserForSegments(userId)
  }

  private evaluateUserForSegments(userId: string) {
    const userEvents = this.eventHistory.get(userId) || []
    const userProfile = this.userProfiles.get(userId) || {
      id: userId,
      segments: [],
      properties: {},
      events: userEvents
    }

    // Evaluate against all segments
    this.segments.forEach((segment, segmentId) => {
      const qualifies = this.evaluateUserAgainstSegment(userId, segment.criteria)

      const currentlyInSegment = segment.users.includes(userId)

      if (qualifies && !currentlyInSegment) {
        this.addUserToSegment(userId, segmentId)
      } else if (!qualifies && currentlyInSegment) {
        this.removeUserFromSegment(userId, segmentId)
      }
    })
  }

  private evaluateUserAgainstSegment(userId: string, criteria: SegmentCriteria): boolean {
    const userEvents = this.eventHistory.get(userId) || []

    // Evaluate demographic criteria
    if (criteria.demographics) {
      // Implement demographic evaluation logic
      // This would require access to user demographic data
    }

    // Evaluate behavior criteria
    if (criteria.behavior) {
      if (criteria.behavior.pageViews) {
        const pageViewEvents = userEvents.filter(event => event.type === 'Page Viewed')
        const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000)
        const recentPageViews = pageViewEvents.filter(event => event.timestamp > thirtyDaysAgo)

        if (criteria.behavior.pageViews.count) {
          const { min, max } = criteria.behavior.pageViews.count
          if (recentPageViews.length < min) return false
          if (max && recentPageViews.length > max) return false
        }
      }

      if (criteria.behavior.events) {
        const matchingEvents = userEvents.filter(event =>
          criteria.behavior!.events!.types.includes(event.type)
        )

        if (criteria.behavior.events.count) {
          const { min, max } = criteria.behavior.events.count
          if (matchingEvents.length < min) return false
          if (max && matchingEvents.length > max) return false
        }
      }
    }

    // Evaluate engagement criteria
    if (criteria.engagement) {
      // Implement engagement evaluation logic
      // This would require session duration tracking, frequency analysis, etc.
    }

    return true
  }

  private getCurrentUserId(): string | null {
    // Get current user ID from your authentication system
    // This is a placeholder implementation
    return localStorage.getItem('currentUserId')
  }

  // Get segment analytics
  getSegmentAnalytics(segmentId: string): SegmentProperties | null {
    const segment = this.segments.get(segmentId)
    return segment ? segment.properties : null
  }

  // Get all segments
  getAllSegments(): UserSegment[] {
    return Array.from(this.segments.values())
  }

  // Export segment data
  exportSegmentData(segmentId: string): any {
    const segment = this.segments.get(segmentId)
    if (!segment) return null

    return {
      segment: {
        id: segment.id,
        name: segment.name,
        criteria: segment.criteria,
        properties: segment.properties
      },
      users: segment.users.map(userId => ({
        id: userId,
        profile: this.userProfiles.get(userId),
        events: this.eventHistory.get(userId) || []
      }))
    }
  }

  // Create segment from FullStory search
  async createSegmentFromSearch(searchQuery: string, segmentName: string): Promise<UserSegment> {
    // This would use FullStory's search API to find matching users
    // For now, we'll simulate with a basic implementation

    const segment = this.createSegment({
      name: segmentName,
      id: segmentName.toLowerCase().replace(/\s+/g, '-'),
      criteria: {
        custom: {
          fullstorySearch: searchQuery
        }
      },
      properties: {
        size: 0,
        conversionRate: 0,
        averageOrderValue: 0,
        retentionRate: 0,
        churnRisk: 'medium',
        lifetimeValue: 0,
        createdAt: new Date(),
        updatedAt: new Date()
      }
    })

    FS.event('Segment Created from Search', {
      segmentId: segment.id,
      searchQuery,
      timestamp: Date.now()
    })

    return segment
  }
}

// Type definitions
interface UserProfile {
  id: string
  segments: string[]
  properties: Record<string, any>
  events: UserEvent[]
}

interface UserEvent {
  type: string
  timestamp: number
  properties: Record<string, any>
}

// Initialize the segmentation engine
const segmentationEngine = new UserSegmentationEngine()

// Example usage
/*
// Create a custom segment
const highValueCustomers = segmentationEngine.createSegment({
  name: 'High Value Customers',
  id: 'high-value-customers',
  criteria: {
    behavior: {
      conversions: {
        types: ['purchase', 'upgrade'],
        totalValue: { min: 1000 },
        timeframe: 90
      }
    },
    engagement: {
      sessionsPerWeek: { min: 3 },
      lastActivity: { days: 7 }
    }
  },
  properties: {
    size: 0,
    conversionRate: 0,
    averageOrderValue: 0,
    retentionRate: 0,
    churnRisk: 'low',
    lifetimeValue: 0,
    createdAt: new Date(),
    updatedAt: new Date()
  }
})

// Get segment analytics
const analytics = segmentationEngine.getSegmentAnalytics('high-value-customers')
console.log('High value customers:', analytics)
*/

export { UserSegmentationEngine, segmentationEngine }