/* eslint-disable @typescript-eslint/no-unused-vars */
/**
 * Feature extraction pipeline for churn prediction and health scoring
 * [1.1] Telemetry Feature Extraction
 */

import type { TelemetryEventDoc } from '../telemetry/types.js';

// ============================================================================
// Feature Definitions
// ============================================================================

export interface UserBehaviorFeatures {
  daysSinceLastSession: number;
  daysSinceLastCoreAction: number;
  hoursSinceLastLogin: number;
  sessionsLast24Hours: number;
  sessionsLast7Days: number;
  sessionsLast30Days: number;
  avgSessionsPerWeek: number;
  avgSessionsPerDay: number;
  avgSessionDurationMinutes: number;
  totalSessionDurationMinutes: number;
  actionsPerSession: number;
  uniqueFeaturesUsed: number;
  sessionFrequencyTrend: number;
  engagementDepthTrend: number;
}

export interface EngagementFeatures {
  featureUsageDiversity: number;
  coreActionCompletionRate: number;
  featureAdoptionVelocity: number;
  powerUserScore: number;
  onboardingCompletionRate: number;
  firstValueMomentAchieved: boolean;
  timeToFirstValueHours: number;
}

export interface PerformanceFeatures {
  errorRateLast7Days: number;
  errorRateLast30Days: number;
  crashCountLast7Days: number;
  crashCountLast30Days: number;
  avgLatencyMs: number;
  slowRequestCount: number;
  timeoutCount: number;
  errorRecoveryRate: number;
  supportTicketCount: number;
}

export interface SocialFeatures {
  shareCount: number;
  inviteCount: number;
  collaborationScore: number;
  teamMemberCount: number;
  integrationsConnected: number;
  externalSharesLast30Days: number;
}

export interface RevenueFeatures {
  planTier: number;
  lifetimeValue: number;
  mrrContribution: number;
  upgradeCount: number;
  downgradeCount: number;
  daysSinceLastPayment: number;
  daysSincePlanChange: number;
  supportTicketCount: number;
  supportSatisfactionScore: number;
  escalatedTicketCount: number;
}

export interface RollingWindowFeatures {
  rollingAvgSessions7d: number;
  rollingAvgDuration7d: number;
  rollingAvgActions7d: number;
  wowSessionChange: number;
  wowDurationChange: number;
  wowActionsChange: number;
  cohortSessionPercentile: number;
  cohortEngagementPercentile: number;
  cohortRetentionPercentile: number;
}

export interface ProductSpecificFeatures {
  fastCompletionRate?: number;
  protocolAdherenceScore?: number;
  streakLength?: number;
  autophagyEngagementScore?: number;
  agentDiversityScore?: number;
  voiceSessionRatio?: number;
  skillProgressionRate?: number;
  sessionCompletionRate?: number;
  timerCompletionRate?: number;
  cascadeEffectiveness?: number;
  routineAdherenceScore?: number;
  urgencyResponseRate?: number;
  brainUsageDiversity?: number;
  triageAccuracyScore?: number;
  memoryCaptureFrequency?: number;
  reflectionCompletionRate?: number;
  activitySessionFrequency?: number;
  goalCompletionRate?: number;
  streakMaintenanceScore?: number;
  socialSharingCount?: number;
  dictationFrequency?: number;
  accuracyRate?: number;
  hotkeyUsageRate?: number;
  vocabularyGrowthRate?: number;
}

export interface TimeWindowFeatures {
  recent: {
    sessionCount: number;
    totalDuration: number;
    actionCount: number;
    errorCount: number;
    uniqueFeatures: string[];
  };
  weekly: {
    sessionCount: number;
    totalDuration: number;
    actionCount: number;
    errorCount: number;
    uniqueFeatures: string[];
    daysActive: number;
  };
  monthly: {
    sessionCount: number;
    totalDuration: number;
    actionCount: number;
    errorCount: number;
    uniqueFeatures: string[];
    daysActive: number;
  };
  lifetime: {
    totalSessions: number;
    totalDuration: number;
    totalActions: number;
    totalErrors: number;
    allFeaturesUsed: string[];
    accountAgeDays: number;
  };
}

export interface CompleteFeatureVector {
  userId: string;
  productId: string;
  computedAt: Date;
  observationWindow: { start: Date; end: Date };
  behavior: UserBehaviorFeatures;
  engagement: EngagementFeatures;
  performance: PerformanceFeatures;
  social: SocialFeatures;
  revenue: RevenueFeatures;
  rolling: RollingWindowFeatures;
  productSpecific: ProductSpecificFeatures;
  timeWindows: TimeWindowFeatures;
  featureSchemaVersion: string;
  dataQualityScore: number;
}

const SCHEMA_VERSION = '1.0.0';

export function extractFeaturesFromTelemetry(
  userId: string,
  productId: string,
  events: TelemetryEventDoc[],
  referenceDate: Date = new Date()
): CompleteFeatureVector {
  const observationStart = new Date(referenceDate);
  observationStart.setDate(observationStart.getDate() - 30);

  const windowedEvents = events.filter(
    e => new Date(e.occurredAt) >= observationStart && new Date(e.occurredAt) <= referenceDate
  );

  const timeWindows = extractTimeWindows(windowedEvents, referenceDate);
  const behavior = extractBehaviorFeatures(windowedEvents, timeWindows, referenceDate);
  const engagement = extractEngagementFeatures(windowedEvents, timeWindows);
  const performance = extractPerformanceFeatures(windowedEvents, timeWindows);
  const social = extractSocialFeatures(windowedEvents);
  const revenue = extractRevenueFeatures(windowedEvents);
  const rolling = extractRollingWindowFeatures(timeWindows);
  const productSpecific = extractProductSpecificFeatures(windowedEvents, productId);
  const dataQualityScore = calculateDataQualityScore(behavior, engagement, performance);

  return {
    userId,
    productId,
    computedAt: referenceDate,
    observationWindow: { start: observationStart, end: referenceDate },
    behavior,
    engagement,
    performance,
    social,
    revenue,
    rolling,
    productSpecific,
    timeWindows,
    featureSchemaVersion: SCHEMA_VERSION,
    dataQualityScore,
  };
}

function extractTimeWindows(events: TelemetryEventDoc[], referenceDate: Date): TimeWindowFeatures {
  const oneDayAgo = new Date(referenceDate.getTime() - 24 * 60 * 60 * 1000);
  const sevenDaysAgo = new Date(referenceDate.getTime() - 7 * 24 * 60 * 60 * 1000);
  const thirtyDaysAgo = new Date(referenceDate.getTime() - 30 * 24 * 60 * 60 * 1000);

  const recentEvents = events.filter(e => new Date(e.occurredAt) >= oneDayAgo);
  const weeklyEvents = events.filter(e => new Date(e.occurredAt) >= sevenDaysAgo);
  const monthlyEvents = events.filter(e => new Date(e.occurredAt) >= thirtyDaysAgo);

  return {
    recent: aggregateEvents(recentEvents),
    weekly: aggregateEventsWithDays(weeklyEvents),
    monthly: aggregateEventsWithDays(monthlyEvents),
    lifetime: {
      totalSessions: countSessions(events),
      totalDuration: sumDurations(events),
      totalActions: countActions(events),
      totalErrors: countErrors(events),
      allFeaturesUsed: extractUniqueFeatures(events),
      accountAgeDays: 30,
    },
  };
}

function aggregateEvents(events: TelemetryEventDoc[]) {
  const sessions = new Set<string>();
  const features = new Set<string>();
  let totalDuration = 0;
  let actionCount = 0;
  let errorCount = 0;

  for (const event of events) {
    if (event.sessionId) sessions.add(event.sessionId);
    if (event.feature) features.add(event.feature);
    if (event.metrics?.duration) totalDuration += event.metrics.duration;
    if (event.eventName?.includes('action')) actionCount++;
    if (event.eventType === 'error' || event.eventType === 'fatal') errorCount++;
  }

  return {
    sessionCount: sessions.size,
    totalDuration,
    actionCount,
    errorCount,
    uniqueFeatures: Array.from(features),
  };
}

function aggregateEventsWithDays(events: TelemetryEventDoc[]) {
  const sessions = new Set<string>();
  const features = new Set<string>();
  const activeDays = new Set<string>();
  let totalDuration = 0;
  let actionCount = 0;
  let errorCount = 0;

  for (const event of events) {
    if (event.sessionId) sessions.add(event.sessionId);
    if (event.feature) features.add(event.feature);
    const day = event.occurredAt.split('T')[0];
    activeDays.add(day);
    if (event.metrics?.duration) totalDuration += event.metrics.duration;
    if (event.eventName?.includes('action')) actionCount++;
    if (event.eventType === 'error' || event.eventType === 'fatal') errorCount++;
  }

  return {
    sessionCount: sessions.size,
    totalDuration,
    actionCount,
    errorCount,
    uniqueFeatures: Array.from(features),
    daysActive: activeDays.size,
  };
}

function extractBehaviorFeatures(
  events: TelemetryEventDoc[],
  timeWindows: TimeWindowFeatures,
  referenceDate: Date
): UserBehaviorFeatures {
  const lastSession = findLastSession(events);
  const lastCoreAction = findLastCoreAction(events);

  const daysSinceLastSession = lastSession
    ? daysBetween(lastSession.occurredAt, referenceDate)
    : 30;
  const daysSinceLastCoreAction = lastCoreAction
    ? daysBetween(lastCoreAction.occurredAt, referenceDate)
    : 30;

  const monthly = timeWindows.monthly;
  const weekly = timeWindows.weekly;

  const avgSessionsPerWeek = monthly.daysActive
    ? monthly.sessionCount / (monthly.daysActive / 7)
    : 0;
  const avgSessionsPerDay = monthly.daysActive ? monthly.sessionCount / monthly.daysActive : 0;
  const avgSessionDurationMinutes = monthly.sessionCount
    ? monthly.totalDuration / monthly.sessionCount / 60
    : 0;
  const actionsPerSession = monthly.sessionCount ? monthly.actionCount / monthly.sessionCount : 0;

  const sessionFrequencyTrend = calculateTrend(weekly.sessionCount, monthly.sessionCount / 4);
  const engagementDepthTrend = calculateTrend(
    weekly.totalDuration / Math.max(weekly.sessionCount, 1),
    monthly.totalDuration / Math.max(monthly.sessionCount, 4)
  );

  return {
    daysSinceLastSession,
    daysSinceLastCoreAction,
    hoursSinceLastLogin: daysSinceLastSession * 24,
    sessionsLast24Hours: timeWindows.recent.sessionCount,
    sessionsLast7Days: weekly.sessionCount,
    sessionsLast30Days: monthly.sessionCount,
    avgSessionsPerWeek,
    avgSessionsPerDay,
    avgSessionDurationMinutes,
    totalSessionDurationMinutes: monthly.totalDuration / 60,
    actionsPerSession,
    uniqueFeaturesUsed: monthly.uniqueFeatures.length,
    sessionFrequencyTrend,
    engagementDepthTrend,
  };
}

function extractEngagementFeatures(
  events: TelemetryEventDoc[],
  timeWindows: TimeWindowFeatures
): EngagementFeatures {
  const monthly = timeWindows.monthly;
  const allFeatures = extractUniqueFeatures(events);
  const totalPossibleFeatures = 20;

  const featureUsageDiversity = Math.min(allFeatures.length / totalPossibleFeatures, 1);
  const coreActionEvents = events.filter(e => e.eventName?.includes('core_action'));
  const coreActionCompletionRate = monthly.actionCount
    ? coreActionEvents.length / monthly.actionCount
    : 0;

  const advancedFeatures = allFeatures.filter(f =>
    ['export', 'integration', 'automation', 'advanced'].some(a => f.includes(a))
  );
  const powerUserScore = Math.min(advancedFeatures.length / 3, 1);

  return {
    featureUsageDiversity,
    coreActionCompletionRate,
    featureAdoptionVelocity: monthly.uniqueFeatures.length / 4,
    powerUserScore,
    onboardingCompletionRate: calculateOnboardingCompletion(events),
    firstValueMomentAchieved: hasFirstValueMoment(events),
    timeToFirstValueHours: calculateTimeToFirstValue(events),
  };
}

function extractPerformanceFeatures(
  events: TelemetryEventDoc[],
  timeWindows: TimeWindowFeatures
): PerformanceFeatures {
  const monthly = timeWindows.monthly;
  const weekly = timeWindows.weekly;

  const monthlyErrors = countErrors(
    events.filter(e => new Date(e.occurredAt) >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
  );
  const weeklyErrors = weekly.errorCount;

  const errorRateLast30Days = monthly.actionCount ? monthlyErrors / monthly.actionCount : 0;
  const errorRateLast7Days = weekly.actionCount ? weeklyErrors / weekly.actionCount : 0;

  const latencyEvents = events.filter(e => e.metrics?.duration && e.metrics.duration < 30000);
  const avgLatencyMs = latencyEvents.length
    ? latencyEvents.reduce((sum, e) => sum + (e.metrics?.duration || 0), 0) / latencyEvents.length
    : 0;

  return {
    errorRateLast7Days,
    errorRateLast30Days,
    crashCountLast7Days: countCrashes(getWeeklyEvents(events)),
    crashCountLast30Days: countCrashes(getMonthlyEvents(events)),
    avgLatencyMs,
    slowRequestCount: countSlowRequests(events),
    timeoutCount: countTimeouts(events),
    errorRecoveryRate: calculateErrorRecoveryRate(events),
    supportTicketCount: countSupportTickets(events),
  };
}

function extractSocialFeatures(events: TelemetryEventDoc[]): SocialFeatures {
  const shareEvents = events.filter(e => e.eventName?.includes('share'));
  const inviteEvents = events.filter(e => e.eventName?.includes('invite'));
  const integrationEvents = events.filter(e => e.eventName?.includes('integration'));

  return {
    shareCount: shareEvents.length,
    inviteCount: inviteEvents.length,
    collaborationScore: calculateCollaborationScore(events),
    teamMemberCount: extractTeamMemberCount(events),
    integrationsConnected: integrationEvents.length,
    externalSharesLast30Days: shareEvents.filter(e => e.context?.external === true).length,
  };
}

function extractRevenueFeatures(events: TelemetryEventDoc[]): RevenueFeatures {
  const planChangeEvents = events.filter(
    e => e.eventName?.includes('plan') || e.eventName?.includes('subscription')
  );
  const supportEvents = events.filter(e => e.eventName?.includes('support'));

  const upgrades = planChangeEvents.filter(e => e.eventName?.includes('upgrade')).length;
  const downgrades = planChangeEvents.filter(e => e.eventName?.includes('downgrade')).length;

  return {
    planTier: extractPlanTier(events),
    lifetimeValue: extractLifetimeValue(events),
    mrrContribution: extractMrrContribution(events),
    upgradeCount: upgrades,
    downgradeCount: downgrades,
    daysSinceLastPayment: extractDaysSincePayment(events),
    daysSincePlanChange: extractDaysSincePlanChange(events),
    supportTicketCount: supportEvents.length,
    supportSatisfactionScore: calculateSupportSatisfaction(supportEvents),
    escalatedTicketCount: supportEvents.filter(e => e.context?.escalated).length,
  };
}

function extractRollingWindowFeatures(timeWindows: TimeWindowFeatures): RollingWindowFeatures {
  const monthly = timeWindows.monthly;
  const weekly = timeWindows.weekly;

  const rollingAvgSessions7d = weekly.sessionCount / 7;
  const rollingAvgDuration7d = weekly.sessionCount
    ? weekly.totalDuration / weekly.sessionCount / 60
    : 0;
  const rollingAvgActions7d = weekly.sessionCount ? weekly.actionCount / weekly.sessionCount : 0;

  const avgWeekInMonth = monthly.sessionCount / 4;
  const wowSessionChange = avgWeekInMonth
    ? (weekly.sessionCount - avgWeekInMonth) / avgWeekInMonth
    : 0;

  const avgDurationWeekInMonth = monthly.sessionCount
    ? monthly.totalDuration / monthly.sessionCount / 60 / 4
    : 0;
  const wowDurationChange = avgDurationWeekInMonth
    ? (rollingAvgDuration7d - avgDurationWeekInMonth) / avgDurationWeekInMonth
    : 0;

  return {
    rollingAvgSessions7d,
    rollingAvgDuration7d,
    rollingAvgActions7d,
    wowSessionChange,
    wowDurationChange,
    wowActionsChange: wowSessionChange,
    cohortSessionPercentile: estimateCohortPercentile(rollingAvgSessions7d, 'sessions'),
    cohortEngagementPercentile: estimateCohortPercentile(
      timeWindows.monthly.uniqueFeatures.length,
      'features'
    ),
    cohortRetentionPercentile: estimateCohortPercentile(monthly.daysActive || 0, 'retention'),
  };
}

export function extractProductSpecificFeatures(
  events: TelemetryEventDoc[],
  productId: string
): ProductSpecificFeatures {
  switch (productId) {
    case 'nomgap':
      return extractNomGapFeatures(events);
    case 'jarvisjr':
      return extractJarvisJrFeatures(events);
    case 'chronomind':
      return extractChronoMindFeatures(events);
    case 'mindlyst':
      return extractMindLystFeatures(events);
    case 'peakpulse':
      return extractPeakPulseFeatures(events);
    case 'lysnrai':
      return extractLysnrAIFeatures(events);
    default:
      return {};
  }
}

function extractNomGapFeatures(events: TelemetryEventDoc[]): ProductSpecificFeatures {
  const fastEvents = events.filter(e => e.feature === 'fasting');
  const completedFasts = fastEvents.filter(e => e.eventName === 'fast_completed');
  const totalFasts = fastEvents.filter(e => e.eventName === 'fast_started').length;
  const protocolEvents = events.filter(e => e.feature === 'protocol');
  const adheredProtocols = protocolEvents.filter(e => e.context?.adhered).length;
  const streakEvents = events.filter(e => e.eventName?.includes('streak'));
  const currentStreak = Math.max(
    ...streakEvents.map(e => (e.context?.streakLength as number) || 0),
    0
  );

  return {
    fastCompletionRate: totalFasts ? completedFasts.length / totalFasts : 0,
    protocolAdherenceScore: protocolEvents.length ? adheredProtocols / protocolEvents.length : 0,
    streakLength: currentStreak,
    autophagyEngagementScore: calculateAutophagyEngagement(events),
  };
}

function extractJarvisJrFeatures(events: TelemetryEventDoc[]): ProductSpecificFeatures {
  const agentEvents = events.filter(e => e.feature === 'agent');
  const uniqueAgents = new Set(agentEvents.map(e => e.context?.agentId as string)).size;
  const voiceEvents = events.filter(e => e.context?.mode === 'voice');
  const textEvents = events.filter(e => e.context?.mode === 'text');
  const totalSessions = voiceEvents.length + textEvents.length;
  const skillEvents = events.filter(e => e.eventName?.includes('skill'));

  return {
    agentDiversityScore: Math.min(uniqueAgents / 3, 1),
    voiceSessionRatio: totalSessions ? voiceEvents.length / totalSessions : 0,
    skillProgressionRate: calculateSkillProgression(skillEvents),
    sessionCompletionRate: calculateSessionCompletionRate(events),
  };
}

function extractChronoMindFeatures(events: TelemetryEventDoc[]): ProductSpecificFeatures {
  const timerEvents = events.filter(e => e.feature === 'timer');
  const completedTimers = timerEvents.filter(e => e.eventName === 'timer_completed').length;
  const totalTimers = timerEvents.filter(e => e.eventName === 'timer_started').length;
  const cascadeEvents = events.filter(e => e.feature === 'cascade');
  const acknowledgedCascades = cascadeEvents.filter(e => e.context?.acknowledged).length;
  const routineEvents = events.filter(e => e.feature === 'routine');

  return {
    timerCompletionRate: totalTimers ? completedTimers / totalTimers : 0,
    cascadeEffectiveness: cascadeEvents.length ? acknowledgedCascades / cascadeEvents.length : 0,
    routineAdherenceScore: calculateRoutineAdherence(routineEvents),
    urgencyResponseRate: calculateUrgencyResponse(events),
  };
}

function extractMindLystFeatures(events: TelemetryEventDoc[]): ProductSpecificFeatures {
  const brainEvents = events.filter(e => e.feature === 'brain');
  const uniqueBrains = new Set(brainEvents.map(e => e.context?.brainId as string)).size;
  const triageEvents = events.filter(e => e.eventName?.includes('triage'));
  const accurateTriages = triageEvents.filter(e => e.context?.accurate).length;
  const memoryEvents = events.filter(e => e.eventName?.includes('memory_capture'));
  const reflectionEvents = events.filter(e => e.eventName?.includes('reflection'));
  const completedReflections = reflectionEvents.filter(e => e.context?.completed).length;

  return {
    brainUsageDiversity: Math.min(uniqueBrains / 3, 1),
    triageAccuracyScore: triageEvents.length ? accurateTriages / triageEvents.length : 0,
    memoryCaptureFrequency: memoryEvents.length / 30,
    reflectionCompletionRate: reflectionEvents.length
      ? completedReflections / reflectionEvents.length
      : 0,
  };
}

function extractPeakPulseFeatures(events: TelemetryEventDoc[]): ProductSpecificFeatures {
  const sessionEvents = events.filter(e => e.feature === 'activity_session');
  const goalEvents = events.filter(e => e.feature === 'goal');
  const completedGoals = goalEvents.filter(e => e.context?.completed).length;
  const streakEvents = events.filter(e => e.eventName?.includes('streak'));
  const currentStreak = Math.max(
    ...streakEvents.map(e => (e.context?.streakLength as number) || 0),
    0
  );
  const shareEvents = events.filter(e => e.eventName?.includes('share'));

  return {
    activitySessionFrequency: sessionEvents.length / 30,
    goalCompletionRate: goalEvents.length ? completedGoals / goalEvents.length : 0,
    streakMaintenanceScore: Math.min(currentStreak / 7, 1),
    socialSharingCount: shareEvents.length,
  };
}

function extractLysnrAIFeatures(events: TelemetryEventDoc[]): ProductSpecificFeatures {
  const dictationEvents = events.filter(e => e.feature === 'dictation');
  const completedDictations = dictationEvents.filter(
    e => e.eventName === 'dictation_completed'
  ).length;
  const accuracyEvents = dictationEvents.filter(e => e.metrics?.accuracy !== undefined);
  const avgAccuracy = accuracyEvents.length
    ? accuracyEvents.reduce((sum, e) => sum + ((e.metrics?.accuracy as number) || 0), 0) /
      accuracyEvents.length
    : 0;
  const hotkeyEvents = events.filter(e => e.eventName?.includes('hotkey'));
  const vocabularyEvents = events.filter(e => e.eventName?.includes('vocabulary'));

  return {
    dictationFrequency: dictationEvents.length / 30,
    accuracyRate: avgAccuracy,
    hotkeyUsageRate: hotkeyEvents.length / Math.max(dictationEvents.length, 1),
    vocabularyGrowthRate: calculateVocabularyGrowth(vocabularyEvents),
  };
}

// Helper Functions
function findLastSession(events: TelemetryEventDoc[]): TelemetryEventDoc | undefined {
  return events
    .filter(e => e.eventName?.includes('session_start') || e.sessionId)
    .sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime())[0];
}

function findLastCoreAction(events: TelemetryEventDoc[]): TelemetryEventDoc | undefined {
  return events
    .filter(e => e.context?.isCoreAction === true || e.eventName?.includes('core'))
    .sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime())[0];
}

function countSessions(events: TelemetryEventDoc[]): number {
  return new Set(events.map(e => e.sessionId).filter(Boolean)).size;
}

function sumDurations(events: TelemetryEventDoc[]): number {
  return events.reduce((sum, e) => sum + (e.metrics?.duration || 0), 0);
}

function countActions(events: TelemetryEventDoc[]): number {
  return events.filter(e => e.eventName?.includes('action')).length;
}

function countErrors(events: TelemetryEventDoc[]): number {
  return events.filter(e => e.eventType === 'error' || e.eventType === 'fatal').length;
}

function extractUniqueFeatures(events: TelemetryEventDoc[]): string[] {
  return Array.from(new Set(events.map(e => e.feature).filter(Boolean) as string[]));
}

function daysBetween(timestamp: string, reference: Date): number {
  return Math.floor((reference.getTime() - new Date(timestamp).getTime()) / (1000 * 60 * 60 * 24));
}

function calculateTrend(current: number, baseline: number): number {
  if (baseline === 0) return current > 0 ? 1 : 0;
  return Math.max(-1, Math.min(1, (current - baseline) / baseline));
}

function calculateDataQualityScore(
  behavior: UserBehaviorFeatures,
  engagement: EngagementFeatures,
  performance: PerformanceFeatures
): number {
  // Return 0 if no session data exists
  if (behavior.sessionsLast30Days === 0) return 0;

  let score = 0;
  let factors = 0;
  if (behavior.sessionsLast30Days > 0) {
    score += Math.min(behavior.sessionsLast30Days / 10, 1);
    factors++;
  }
  if (engagement.featureUsageDiversity > 0) {
    score += Math.min(engagement.featureUsageDiversity * 5, 1);
    factors++;
  }
  if (performance.errorRateLast30Days >= 0) {
    score += 1 - performance.errorRateLast30Days;
    factors++;
  }
  return factors > 0 ? score / factors : 0;
}

function calculateAutophagyEngagement(events: TelemetryEventDoc[]): number {
  return Math.min(events.filter(e => e.context?.stage === 'autophagy').length / 10, 1);
}

function calculateSkillProgression(events: TelemetryEventDoc[]): number {
  return events.length ? events.filter(e => e.context?.progressed).length / events.length : 0;
}

function calculateSessionCompletionRate(events: TelemetryEventDoc[]): number {
  const started = events.filter(e => e.eventName?.includes('started')).length;
  const completed = events.filter(e => e.eventName?.includes('completed')).length;
  return started ? completed / started : 0;
}

function calculateRoutineAdherence(events: TelemetryEventDoc[]): number {
  return events.length ? events.filter(e => e.context?.onTime).length / events.length : 0;
}

function calculateUrgencyResponse(events: TelemetryEventDoc[]): number {
  const urgent = events.filter(e => e.context?.urgent === true);
  return urgent.length ? urgent.filter(e => e.context?.responded).length / urgent.length : 0;
}

function calculateVocabularyGrowth(events: TelemetryEventDoc[]): number {
  return events.reduce((sum, e) => sum + ((e.metrics?.wordsAdded as number) || 0), 0) / 30;
}

function calculateOnboardingCompletion(events: TelemetryEventDoc[]): number {
  const steps = events.filter(e => e.eventName?.includes('onboarding'));
  return Math.min(steps.filter(e => e.context?.completed).length / 5, 1);
}

function hasFirstValueMoment(events: TelemetryEventDoc[]): boolean {
  return events.some(e => e.eventName?.includes('first_value') || e.context?.ahaMoment);
}

function calculateTimeToFirstValue(events: TelemetryEventDoc[]): number {
  const firstSession = events.find(e => e.eventName?.includes('session_start'));
  const firstValue = events.find(e => e.eventName?.includes('first_value'));
  return firstSession && firstValue
    ? (new Date(firstValue.occurredAt).getTime() - new Date(firstSession.occurredAt).getTime()) /
        (1000 * 60 * 60)
    : 0;
}

function countCrashes(events: TelemetryEventDoc[]): number {
  return events.filter(e => e.eventName?.includes('crash') || e.context?.crash).length;
}

function countSlowRequests(events: TelemetryEventDoc[]): number {
  return events.filter(e => e.metrics?.duration && e.metrics.duration > 5000).length;
}

function countTimeouts(events: TelemetryEventDoc[]): number {
  return events.filter(e => e.context?.timeout || e.eventName?.includes('timeout')).length;
}

function calculateErrorRecoveryRate(events: TelemetryEventDoc[]): number {
  const errors = events.filter(e => e.eventType === 'error' || e.eventType === 'fatal');
  return errors.length ? errors.filter(e => e.context?.recovered).length / errors.length : 1;
}

function countSupportTickets(events: TelemetryEventDoc[]): number {
  return events.filter(e => e.eventName?.includes('support_ticket')).length;
}

function calculateCollaborationScore(events: TelemetryEventDoc[]): number {
  return Math.min(events.filter(e => e.context?.collaborative === true).length / 10, 1);
}

function extractTeamMemberCount(events: TelemetryEventDoc[]): number {
  const teamEvents = events.filter(e => e.context?.teamSize !== undefined);
  return teamEvents.length
    ? Math.max(...teamEvents.map(e => (e.context?.teamSize as number) || 0))
    : 0;
}

function extractPlanTier(events: TelemetryEventDoc[]): number {
  const planEvent = events.find(e => e.context?.planTier !== undefined);
  return (planEvent?.context?.planTier as number) || 0;
}

function extractLifetimeValue(events: TelemetryEventDoc[]): number {
  return events.reduce((sum, e) => sum + ((e.metrics?.revenue as number) || 0), 0);
}

function extractMrrContribution(events: TelemetryEventDoc[]): number {
  const mrrEvent = events.find(e => e.metrics?.mrr !== undefined);
  return (mrrEvent?.metrics?.mrr as number) || 0;
}

function extractDaysSincePayment(events: TelemetryEventDoc[]): number {
  const paymentEvent = events
    .filter(e => e.eventName?.includes('payment'))
    .sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime())[0];
  return paymentEvent ? daysBetween(paymentEvent.occurredAt, new Date()) : 30;
}

function extractDaysSincePlanChange(events: TelemetryEventDoc[]): number {
  const planChange = events
    .filter(e => e.eventName?.includes('plan_change'))
    .sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime())[0];
  return planChange ? daysBetween(planChange.occurredAt, new Date()) : 90;
}

function calculateSupportSatisfaction(events: TelemetryEventDoc[]): number {
  const rated = events.filter(e => e.context?.satisfaction !== undefined);
  return rated.length
    ? rated.reduce((acc, e) => acc + ((e.context?.satisfaction as number) || 0), 0) / rated.length
    : 0;
}

function estimateCohortPercentile(value: number, metric: string): number {
  const baselines: Record<string, number> = { sessions: 2, features: 5, retention: 15 };
  return Math.min(Math.round((value / (baselines[metric] || 1)) * 50), 100);
}

function getWeeklyEvents(events: TelemetryEventDoc[]): TelemetryEventDoc[] {
  const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
  return events.filter(e => new Date(e.occurredAt) >= weekAgo);
}

function getMonthlyEvents(events: TelemetryEventDoc[]): TelemetryEventDoc[] {
  const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
  return events.filter(e => new Date(e.occurredAt) >= monthAgo);
}
