/**
 * Presence Manager
 * Tracks local user's cursor position and broadcasts presence updates
 * Receives and renders remote users' cursors/selections
 */

import { Doc, Map as YMap, Text } from 'yjs';

export interface CursorPosition {
  userId: string;
  userName: string;
  position: number;
  selection?: SelectionRange;
  color: string;
  lastActive?: Date;
}

export interface SelectionRange {
  anchor: number;
  head: number;
}

export interface RemoteUser {
  userId: string;
  userName: string;
  avatarUrl?: string;
  cursor?: CursorPosition;
  selection?: SelectionRange;
  isEditing: boolean;
  lastActive: Date;
}

export interface PresenceManager {
  initialize(doc: Doc, userId: string, userName: string): void;
  updateCursorPosition(cursor: CursorPosition): void;
  updateSelection(selection: SelectionRange): void;
  getRemoteUsers(): Map<string, RemoteUser>;
  getUserCursor(userId: string): CursorPosition | undefined;
  onUserJoin(callback: (user: RemoteUser) => void): void;
  onUserLeave(callback: (userId: string) => void): void;
  onUserUpdate(callback: (user: RemoteUser) => void): void;
  destroy(): void;
}

export class Presence implements PresenceManager {
  private doc: Doc | null = null;
  private userId: string | null = null;
  private userName: string | null = null;
  private presenceMap: YMap<any> | null = null;
  private userColor: string | null = null;
  
  private userJoinListeners: Set<(user: RemoteUser) => void> = new Set();
  private userLeaveListeners: Set<(userId: string) => void> = new Set();
  private userUpdateListeners: Set<(user: RemoteUser) => void> = new Set();
  
  private idleTimeout: NodeJS.Timeout | null = null;
  private readonly IDLE_TIMEOUT_MS = 30000;

  private static readonly COLORS = [
    '#ef4444', '#f97316', '#f59e0b', '#84cc16', '#10b981',
    '#06b6d4', '#3b82f6', '#6366f1', '#8b5cf6', '#d946ef'
  ];

  initialize(doc: Doc, userId: string, userName: string): void {
    this.doc = doc;
    this.userId = userId;
    this.userName = userName;
    
    // Get or create presence map
    this.presenceMap = doc.getMap<RemoteUser>('presence');
    
    // Assign a color to this user (deterministic based on userId)
    const colorIndex = Math.abs(this.hashString(userId)) % Presence.COLORS.length;
    this.userColor = Presence.COLORS[colorIndex] || '#888888';
    
    // Register for presence updates
    this.presenceMap.observe(this.handlePresenceChange.bind(this));
    
    // Initialize idle timeout
    this.resetIdleTimeout();
  }

  updateCursorPosition(cursor: CursorPosition): void {
    if (!this.presenceMap || !this.userId) {
      return;
    }

    const currentUser = this.presenceMap.get(this.userId) || {
      userId: this.userId,
      userName: this.userName || 'Unknown',
      isEditing: false,
      lastActive: new Date(),
    };

    const now = new Date();
    const updatedUser: RemoteUser = {
      ...currentUser,
      cursor: {
        ...cursor,
        userId: this.userId,
        userName: this.userName || 'Unknown',
        color: this.userColor!,
        selection: cursor.selection,
        lastActive: now,
      },
      isEditing: true,
      lastActive: now,
    };

    this.presenceMap.set(this.userId, updatedUser);
    this.resetIdleTimeout();
  }

  updateSelection(selection: SelectionRange): void {
    if (!this.presenceMap || !this.userId) {
      return;
    }

    const currentUser = this.presenceMap.get(this.userId);
    if (currentUser?.cursor) {
      const updatedUser: RemoteUser = {
        ...currentUser,
        selection,
        cursor: {
          ...currentUser.cursor,
          selection,
        },
        lastActive: new Date(),
      };
      this.presenceMap.set(this.userId, updatedUser);
    }
  }

  getRemoteUsers(): Map<string, RemoteUser> {
    const users = new Map<string, RemoteUser>();
    
    if (!this.presenceMap) {
      return users;
    }

    const presenceData = this.presenceMap.toJSON();
    Object.entries(presenceData).forEach(([userId, user]) => {
      if (userId !== this.userId) {
        users.set(userId, user as RemoteUser);
      }
    });

    return users;
  }

  getUserCursor(userId: string): CursorPosition | undefined {
    if (!this.presenceMap) {
      return undefined;
    }

    const user = this.presenceMap.get(userId);
    return user?.cursor;
  }

  onUserJoin(callback: (user: RemoteUser) => void): void {
    this.userJoinListeners.add(callback);
  }

  onUserLeave(callback: (userId: string) => void): void {
    this.userLeaveListeners.add(callback);
  }

  onUserUpdate(callback: (user: RemoteUser) => void): void {
    this.userUpdateListeners.add(callback);
  }

  destroy(): void {
    if (this.presenceMap && this.userId) {
      // Clear own presence
      this.presenceMap.delete(this.userId);
    }

    if (this.idleTimeout) {
      clearTimeout(this.idleTimeout);
      this.idleTimeout = null;
    }

    if (this.presenceMap) {
      this.presenceMap.unobserve(this.handlePresenceChange.bind(this));
    }

    this.doc = null;
    this.presenceMap = null;
    this.userId = null;
    this.userName = null;
    this.userColor = null;
  }

  private handlePresenceChange(event: any): void {
    if (!this.userId) return;

    event.changes.keys.forEach((change: any, key: string) => {
      if (change.action === 'add') {
        const newUser = this.presenceMap!.get(key);
        if (newUser && key !== this.userId) {
          this.userJoinListeners.forEach(listener => listener(newUser));
        }
      } else if (change.action === 'delete') {
        this.userLeaveListeners.forEach(listener => listener(key));
      } else if (change.action === 'update') {
        const updatedUser = this.presenceMap!.get(key);
        if (updatedUser && key !== this.userId) {
          this.userUpdateListeners.forEach(listener => listener(updatedUser));
        }
      }
    });
  }

  private resetIdleTimeout(): void {
    if (this.idleTimeout) {
      clearTimeout(this.idleTimeout);
    }

    this.idleTimeout = setTimeout(() => {
      this.markAsIdle();
    }, this.IDLE_TIMEOUT_MS);
  }

  private markAsIdle(): void {
    if (!this.presenceMap || !this.userId) {
      return;
    }

    const currentUser = this.presenceMap.get(this.userId);
    if (currentUser) {
      const idleUser: RemoteUser = {
        ...currentUser,
        isEditing: false,
        lastActive: new Date(),
      };
      this.presenceMap.set(this.userId, idleUser);
    }
  }

  private hashString(str: string): number {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return Math.abs(hash);
  }
}
