/**
 * Presence Manager
 * Tracks connected users, their cursor positions, and idle state
 * Integrates with WebSocket for real-time presence updates
 */

import { WebsocketProvider } from 'y-websocket';
import { WebSocketConnection } from './websocket-connection';

/**
 * User presence state
 */
export interface UserPresence {
  userId: string;
  name: string;
  color: string;
  cursorPosition: number | null;
  selectionStart: number | null;
  selectionEnd: number | null;
  editingContext: string | null; // e.g., scene ID or element being edited
  lastActivity: Date;
  status: 'active' | 'idle' | 'away';
}

/**
 * Presence update message for WebSocket
 */
export interface PresenceUpdateMessage {
  type: 'presence:update';
  userId: string;
  presence: Omit<UserPresence, 'lastActivity'>;
  timestamp: number;
}

/**
 * User join event
 */
export interface UserJoinMessage {
  type: 'presence:join';
  userId: string;
  presence: Omit<UserPresence, 'lastActivity' | 'status'>;
  timestamp: number;
}

/**
 * User leave event
 */
export interface UserLeaveMessage {
  type: 'presence:leave';
  userId: string;
  timestamp: number;
}

/**
 * Full presence state from server
 */
export interface PresenceStateMessage {
  type: 'presence:state';
  users: Record<string, Omit<UserPresence, 'lastActivity'>>;
  timestamp: number;
}

/**
 * Presence message type discriminator
 */
export type PresenceMessage = 
  | PresenceUpdateMessage 
  | UserJoinMessage 
  | UserLeaveMessage 
  | PresenceStateMessage;

/**
 * Options for PresenceManager
 */
export interface PresenceManagerOptions {
  userId: string;
  userName: string;
  userColor: string;
  idleTimeoutMs?: number;
  broadcastIntervalMs?: number;
}

/**
 * Callback types for presence events
 */
export type OnUserJoin = (userId: string, presence: Omit<UserPresence, 'lastActivity'>) => void;
export type OnUserLeave = (userId: string) => void;
export type OnPresenceUpdate = (userId: string, presence: UserPresence) => void;
export type OnPresenceState = (users: Record<string, UserPresence>) => void;

/**
 * PresenceManager class
 * Manages local user presence and tracks remote users
 */
export class PresenceManager {
  private userId: string;
  private userName: string;
  private userColor: string;
  private idleTimeoutMs: number;
  private broadcastIntervalMs: number;
  
  private provider: WebsocketProvider | null = null;
  private connection: WebSocketConnection | null = null;
  
  // Remote users' presence state
  private remoteUsers: Map<string, UserPresence> = new Map();
  
  // Local user's current state
  private localPresence: UserPresence;
  
  // Timers
  private idleTimer: ReturnType<typeof setTimeout> | null = null;
  private broadcastTimer: ReturnType<typeof setInterval> | null = null;
  
  // Event callbacks
  private onUserJoinCallbacks: Set<OnUserJoin> = new Set();
  private onUserLeaveCallbacks: Set<OnUserLeave> = new Set();
  private onPresenceUpdateCallbacks: Set<OnPresenceUpdate> = new Set();
  private onPresenceStateCallbacks: Set<OnPresenceState> = new Set();
  
  // Activity tracking
  private lastActivityTime: Date = new Date();
  private isInitialized: boolean = false;

  constructor(options: PresenceManagerOptions) {
    this.userId = options.userId;
    this.userName = options.userName;
    this.userColor = options.userColor;
    this.idleTimeoutMs = options.idleTimeoutMs || 30000; // 30 seconds default
    this.broadcastIntervalMs = options.broadcastIntervalMs || 1000; // 1 second default
    
    // Initialize local presence
    this.localPresence = {
      userId: this.userId,
      name: this.userName,
      color: this.userColor,
      cursorPosition: null,
      selectionStart: null,
      selectionEnd: null,
      editingContext: null,
      lastActivity: new Date(),
      status: 'active',
    };
  }

  /**
   * Initialize the presence manager with a WebSocket connection
   */
  initialize(connection: WebSocketConnection): void {
    if (this.isInitialized) {
      return;
    }

    this.connection = connection;
    this.provider = connection.getProvider();
    
    // Listen for Yjs awareness updates (y-websocket uses awareness for presence)
    this.provider.on('awareness', this.handleAwarenessUpdate);
    
    // Listen for generic message events for custom presence messages
    this.provider.on('message', this.handlePresenceMessage);
    
    // Start idle monitoring
    this.startIdleMonitor();
    
    // Start periodic presence broadcast
    this.startPresenceBroadcast();
    
    this.isInitialized = true;
    console.log(`[PresenceManager] Initialized for user ${this.userName} (${this.userId})`);
  }

  /**
   * Shutdown the presence manager
   */
  shutdown(): void {
    if (this.provider) {
      this.provider.off('awareness', this.handleAwarenessUpdate);
      this.provider.off('message', this.handlePresenceMessage);
    }
    
    if (this.broadcastTimer) {
      clearInterval(this.broadcastTimer);
      this.broadcastTimer = null;
    }
    
    if (this.idleTimer) {
      clearTimeout(this.idleTimer);
      this.idleTimer = null;
    }
    
    // Send leave message
    this.sendLeaveMessage();
    
    this.isInitialized = false;
    console.log(`[PresenceManager] Shutdown for user ${this.userName}`);
  }

  /**
   * Update cursor position
   */
  updateCursorPosition(position: number, selectionStart?: number, selectionEnd?: number): void {
    this.localPresence.cursorPosition = position;
    this.localPresence.selectionStart = selectionStart ?? null;
    this.localPresence.selectionEnd = selectionEnd ?? null;
    this.recordActivity();
  }

  /**
   * Update editing context (e.g., which scene or element is being edited)
   */
  updateEditingContext(context: string | null): void {
    this.localPresence.editingContext = context;
    this.recordActivity();
  }

  /**
   * Get all connected users
   */
  getConnectedUsers(): UserPresence[] {
    return Array.from(this.remoteUsers.values());
  }

  /**
   * Get a specific user by ID
   */
  getUser(userId: string): UserPresence | undefined {
    return this.remoteUsers.get(userId);
  }

  /**
   * Get the local user's presence
   */
  getLocalPresence(): UserPresence {
    return this.localPresence;
  }

  /**
   * Check if user is currently idle
   */
  isUserIdle(userId: string): boolean {
    const user = userId === this.userId ? this.localPresence : this.remoteUsers.get(userId);
    return user?.status === 'idle';
  }

  /**
   * Record user activity and reset idle timer
   */
  recordActivity(): void {
    this.lastActivityTime = new Date();
    const wasIdle = this.localPresence.status === 'idle';
    
    // Update local status
    this.localPresence.lastActivity = this.lastActivityTime;
    this.localPresence.status = 'active';
    
    // If was idle, notify listeners
    if (wasIdle) {
      this.onPresenceUpdateCallbacks.forEach(callback => {
        callback(this.userId, { ...this.localPresence });
      });
    }
  }

  /**
   * Register callback for user join events
   */
  onUserJoin(callback: OnUserJoin): void {
    this.onUserJoinCallbacks.add(callback);
  }

  /**
   * Remove user join callback
   */
  offUserJoin(callback: OnUserJoin): void {
    this.onUserJoinCallbacks.delete(callback);
  }

  /**
   * Register callback for user leave events
   */
  onUserLeave(callback: OnUserLeave): void {
    this.onUserLeaveCallbacks.add(callback);
  }

  /**
   * Remove user leave callback
   */
  offUserLeave(callback: OnUserLeave): void {
    this.onUserLeaveCallbacks.delete(callback);
  }

  /**
   * Register callback for presence updates
   */
  onPresenceUpdate(callback: OnPresenceUpdate): void {
    this.onPresenceUpdateCallbacks.add(callback);
  }

  /**
   * Remove presence update callback
   */
  offPresenceUpdate(callback: OnPresenceUpdate): void {
    this.onPresenceUpdateCallbacks.delete(callback);
  }

  /**
   * Register callback for full presence state
   */
  onPresenceState(callback: OnPresenceState): void {
    this.onPresenceStateCallbacks.add(callback);
  }

  /**
   * Remove presence state callback
   */
  offPresenceState(callback: OnPresenceState): void {
    this.onPresenceStateCallbacks.delete(callback);
  }

  /**
   * Send presence update to server
   */
  private sendPresenceUpdate(): void {
    if (!this.provider) return;

    // Update awareness state (y-websocket standard)
    const awareness = this.provider.awareness;
    if (awareness) {
      const currentState = awareness.getLocalState();
      awareness.setLocalStateField('presence', {
        userId: this.localPresence.userId,
        name: this.localPresence.name,
        color: this.localPresence.color,
        cursorPosition: this.localPresence.cursorPosition,
        selectionStart: this.localPresence.selectionStart,
        selectionEnd: this.localPresence.selectionEnd,
        editingContext: this.localPresence.editingContext,
        status: this.localPresence.status,
      });
    }

    // Note: Custom messages are sent via awareness state only
    // y-websocket doesn't expose a direct send method for custom messages
  }

  /**
   * Send user leave message
   */
  private sendLeaveMessage(): void {
    // User leave is handled automatically by awareness when connection closes
    // y-websocket doesn't support custom leave messages
  }

  /**
   * Start idle monitoring timer
   */
  private startIdleMonitor(): void {
    const checkIdle = () => {
      const now = new Date();
      const idleDuration = now.getTime() - this.lastActivityTime.getTime();
      
      if (idleDuration >= this.idleTimeoutMs && this.localPresence.status !== 'idle') {
        this.localPresence.status = 'idle';
        this.onPresenceUpdateCallbacks.forEach(callback => {
          callback(this.userId, { ...this.localPresence });
        });
        console.log(`[PresenceManager] User ${this.userName} marked as idle`);
      }
      
      this.idleTimer = setTimeout(checkIdle, 1000);
    };
    
    this.idleTimer = setTimeout(checkIdle, 1000);
  }

  /**
   * Start periodic presence broadcast
   */
  private startPresenceBroadcast(): void {
    this.broadcastTimer = setInterval(() => {
      this.sendPresenceUpdate();
    }, this.broadcastIntervalMs);
  }

  /**
   * Process awareness update from y-websocket
   */
  private processAwarenessUpdate(states: Map<number, any>): void {
    states.forEach((state, clientId) => {
      if (clientId.toString() === this.userId) {
        return; // Skip own state
      }
      
      if (state.presence) {
        const presence: UserPresence = {
          ...state.presence,
          lastActivity: new Date(state.lastActivity || Date.now()),
        };
        
        const wasKnown = this.remoteUsers.has(presence.userId);
        this.remoteUsers.set(presence.userId, presence);
        
        if (!wasKnown) {
          this.onUserJoinCallbacks.forEach(callback => {
            callback(presence.userId, {
              userId: presence.userId,
              name: presence.name,
              color: presence.color,
              cursorPosition: presence.cursorPosition,
              selectionStart: presence.selectionStart,
              selectionEnd: presence.selectionEnd,
              editingContext: presence.editingContext,
              status: presence.status,
            });
          });
        } else {
          this.onPresenceUpdateCallbacks.forEach(callback => {
            callback(presence.userId, presence);
          });
        }
      }
    });
    
    // Clean up disconnected users
    states.forEach((_, clientId) => {
      if (!this.remoteUsers.has(clientId.toString())) {
        const removedUserId = clientId.toString();
        // Keep users that have sent presence
      }
    });
  }

  /**
   * Process custom presence message
   */
  private processPresenceMessage(message: PresenceMessage): void {
    switch (message.type) {
      case 'presence:update':
        const existingUser = this.remoteUsers.get(message.userId);
        const updatedPresence: UserPresence = {
          ...message.presence,
          lastActivity: new Date(message.timestamp),
        };
        
        this.remoteUsers.set(message.userId, updatedPresence);
        
        if (!existingUser) {
          // New user
          this.onUserJoinCallbacks.forEach(callback => {
            callback(message.userId, message.presence);
          });
        }
        
        this.onPresenceUpdateCallbacks.forEach(callback => {
          callback(message.userId, updatedPresence);
        });
        break;
        
      case 'presence:join':
        const joinPresence: UserPresence = {
          ...message.presence,
          lastActivity: new Date(message.timestamp),
          status: 'active',
        };
        
        this.remoteUsers.set(message.userId, joinPresence);
        this.onUserJoinCallbacks.forEach(callback => {
          callback(message.userId, {
            userId: message.presence.userId,
            name: message.presence.name,
            color: message.presence.color,
            cursorPosition: message.presence.cursorPosition,
            selectionStart: message.presence.selectionStart,
            selectionEnd: message.presence.selectionEnd,
            editingContext: message.presence.editingContext,
            status: 'active',
          });
        });
        break;
        
      case 'presence:leave':
        this.remoteUsers.delete(message.userId);
        this.onUserLeaveCallbacks.forEach(callback => {
          callback(message.userId);
        });
        break;
        
      case 'presence:state':
        // Full state sync from server
        this.remoteUsers.clear();
        Object.entries(message.users).forEach(([userId, presence]) => {
          const userPresence: UserPresence = {
            ...presence,
            lastActivity: new Date(Date.now()),
          };
          this.remoteUsers.set(userId, userPresence);
        });
        
        this.onPresenceStateCallbacks.forEach(callback => {
          callback(Object.fromEntries(this.remoteUsers.entries()));
        });
        break;
    }
  }

  // Store handler references for cleanup
  private handleAwarenessUpdate = (event: { states: Map<number, any> }) => {
    this.processAwarenessUpdate(event.states);
  };

  private handlePresenceMessage = (event: { message: PresenceMessage }) => {
    this.processPresenceMessage(event.message);
  };
}

/**
 * Generate a random color for a user (for cursor/display)
 */
export function generateUserColor(userId: string): string {
  // Use a deterministic hash to generate consistent colors
  const colors = [
    '#ef4444', // red
    '#f97316', // orange
    '#eab308', // yellow
    '#22c55e', // green
    '#06b6d4', // cyan
    '#3b82f6', // blue
    '#8b5cf6', // violet
    '#ec4899', // pink
  ];
  
  let hash = 0;
  for (let i = 0; i < userId.length; i++) {
    hash = userId.charCodeAt(i) + ((hash << 5) - hash);
  }
  
  return colors[Math.abs(hash) % colors.length]!;
}

/**
 * Default user colors for the application
 */
export const DEFAULT_USER_COLORS = [
  '#ef4444', // red
  '#f97316', // orange
  '#eab308', // yellow
  '#22c55e', // green
  '#06b6d4', // cyan
  '#3b82f6', // blue
  '#8b5cf6', // violet
  '#ec4899', // pink
  '#6366f1', // indigo
  '#14b8a6', // teal
];
