import { useEffect, useRef, useState, useCallback } from 'react';
import * as Y from 'yjs';
import { IndexeddbPersistence } from 'y-indexeddb';
import { io, Socket } from 'socket.io-client';

/**
 * Collaboration User
 */
export interface CollaborationUser {
  id: string;
  name: string;
  email?: string;
  avatar?: string;
  color: string;
  cursor?: {
    field: string;
    position: number;
    selection?: { start: number; end: number };
  };
}

/**
 * Collaboration Options
 */
export interface CollaborationOptions {
  documentId: string;
  documentType: 'task' | 'project';
  userId: string;
  userInfo: Omit<CollaborationUser, 'cursor'>;
  serverUrl?: string;
  token?: string;
  onUserJoined?: (user: CollaborationUser) => void;
  onUserLeft?: (userId: string) => void;
  onCursorUpdate?: (userId: string, cursor: any) => void;
  onAwarenessUpdate?: (userId: string, awareness: any) => void;
  onDocumentChange?: (event: any) => void;
  autoConnect?: boolean;
}

/**
 * Collaboration State
 */
export interface CollaborationState {
  connected: boolean;
  activeUsers: CollaborationUser[];
  localUser: CollaborationUser;
  isSyncing: boolean;
  hasUnsavedChanges: boolean;
  lastSyncTime?: Date;
}

/**
 * React Hook for Y.js Collaboration
 */
export function useCollaboration(options: CollaborationOptions) {
  const {
    documentId,
    documentType,
    userId,
    userInfo,
    serverUrl = 'http://localhost:3000',
    token,
    onUserJoined,
    onUserLeft,
    onCursorUpdate,
    onAwarenessUpdate,
    onDocumentChange,
    autoConnect = true
  } = options;

  // Y.js references
  const docRef = useRef<Y.Doc | null>(null);
  const persistenceRef = useRef<IndexeddbPersistence | null>(null);
  const socketRef = useRef<Socket | null>(null);

  // State
  const [state, setState] = useState<CollaborationState>({
    connected: false,
    activeUsers: [],
    localUser: { ...userInfo, id: userId, cursor: undefined },
    isSyncing: false,
    hasUnsavedChanges: false
  });

  // Initialize Y.js document
  const initializeDocument = useCallback(() => {
    if (docRef.current) return docRef.current;

    const doc = new Y.Doc();
    docRef.current = doc;

    // Set up persistence
    const persistence = new IndexeddbPersistence(`${documentType}-${documentId}`, doc);
    persistenceRef.current = persistence;

    persistence.on('synced', () => {
      setState(prev => ({ ...prev, isSyncing: false }));
    });

    // Set up document structure based on type
    const dataMap = doc.getMap(documentType);
    
    if (documentType === 'task') {
      dataMap.set('id', documentId);
      dataMap.set('title', new Y.Text());
      dataMap.set('description', new Y.Text());
      dataMap.set('status', 'pending');
      dataMap.set('priority', 'medium');
      dataMap.set('assignees', new Y.Array());
      dataMap.set('tags', new Y.Array());
      dataMap.set('subtasks', new Y.Array());
      dataMap.set('comments', new Y.Array());
      dataMap.set('customFields', new Y.Map());
    } else {
      dataMap.set('id', documentId);
      dataMap.set('name', new Y.Text());
      dataMap.set('description', new Y.Text());
      dataMap.set('tasks', new Y.Array());
      dataMap.set('members', new Y.Array());
      dataMap.set('settings', new Y.Map());
    }

    // Set up change observer
    doc.on('update', (update: Uint8Array, origin: any) => {
      if (origin !== 'remote') {
        setState(prev => ({ ...prev, hasUnsavedChanges: true }));
        
        // Send update to server
        if (socketRef.current?.connected) {
          socketRef.current.emit('collaboration:update', {
            documentId,
            update: Array.from(update),
            userId
          });
        }
      }

      if (onDocumentChange) {
        onDocumentChange({ update, origin });
      }
    });

    return doc;
  }, [documentId, documentType, userId, onDocumentChange]);

  // Initialize Socket.io connection
  const initializeSocket = useCallback(() => {
    if (socketRef.current) return socketRef.current;

    const socket = io(serverUrl, {
      auth: { token },
      transports: ['websocket', 'polling']
    });

    socketRef.current = socket;

    // Socket event handlers
    socket.on('connect', () => {
      console.log('🔌 Collaboration connected');
      setState(prev => ({ ...prev, connected: true }));

      // Join document room
      socket.emit('collaboration:join', {
        documentId,
        documentType,
        userId,
        userInfo
      });
    });

    socket.on('disconnect', () => {
      console.log('🔌 Collaboration disconnected');
      setState(prev => ({ ...prev, connected: false }));
    });

    // Handle initial document state
    socket.on('collaboration:init', (data: {
      documentId: string;
      state: number[];
      users: string[];
    }) => {
      if (docRef.current && data.state) {
        Y.applyUpdate(docRef.current, new Uint8Array(data.state), 'remote');
      }
      setState(prev => ({ ...prev, lastSyncTime: new Date() }));
    });

    // Handle remote updates
    socket.on('collaboration:sync', (data: {
      documentId: string;
      update: number[];
      origin: any;
    }) => {
      if (docRef.current && data.documentId === documentId) {
        Y.applyUpdate(docRef.current, new Uint8Array(data.update), 'remote');
        setState(prev => ({ 
          ...prev, 
          hasUnsavedChanges: false,
          lastSyncTime: new Date()
        }));
      }
    });

    // Handle user events
    socket.on('collaboration:user-joined', (data: {
      documentId: string;
      userId: string;
      userInfo: CollaborationUser;
    }) => {
      if (data.documentId === documentId) {
        setState(prev => ({
          ...prev,
          activeUsers: [...prev.activeUsers, data.userInfo]
        }));
        if (onUserJoined) onUserJoined(data.userInfo);
      }
    });

    socket.on('collaboration:user-left', (data: {
      documentId: string;
      userId: string;
    }) => {
      if (data.documentId === documentId) {
        setState(prev => ({
          ...prev,
          activeUsers: prev.activeUsers.filter(u => u.id !== data.userId)
        }));
        if (onUserLeft) onUserLeft(data.userId);
      }
    });

    // Handle cursor updates
    socket.on('collaboration:cursor-update', (data: {
      documentId: string;
      userId: string;
      cursor: any;
    }) => {
      if (data.documentId === documentId) {
        setState(prev => ({
          ...prev,
          activeUsers: prev.activeUsers.map(u =>
            u.id === data.userId ? { ...u, cursor: data.cursor } : u
          )
        }));
        if (onCursorUpdate) onCursorUpdate(data.userId, data.cursor);
      }
    });

    // Handle awareness updates
    socket.on('collaboration:awareness-update', (data: {
      documentId: string;
      userId: string;
      awareness: any;
    }) => {
      if (data.documentId === documentId && onAwarenessUpdate) {
        onAwarenessUpdate(data.userId, data.awareness);
      }
    });

    socket.on('collaboration:error', (error: { message: string }) => {
      console.error('Collaboration error:', error.message);
    });

    return socket;
  }, [
    serverUrl,
    token,
    documentId,
    documentType,
    userId,
    userInfo,
    onUserJoined,
    onUserLeft,
    onCursorUpdate,
    onAwarenessUpdate
  ]);

  // Connect to collaboration
  const connect = useCallback(() => {
    const doc = initializeDocument();
    const socket = initializeSocket();
    return { doc, socket };
  }, [initializeDocument, initializeSocket]);

  // Disconnect from collaboration
  const disconnect = useCallback(() => {
    if (socketRef.current) {
      socketRef.current.emit('collaboration:leave', { documentId, userId });
      socketRef.current.disconnect();
      socketRef.current = null;
    }

    if (persistenceRef.current) {
      persistenceRef.current.destroy();
      persistenceRef.current = null;
    }

    if (docRef.current) {
      docRef.current.destroy();
      docRef.current = null;
    }

    setState({
      connected: false,
      activeUsers: [],
      localUser: { ...userInfo, id: userId, cursor: undefined },
      isSyncing: false,
      hasUnsavedChanges: false
    });
  }, [documentId, userId, userInfo]);

  // Get Y.js document
  const getDocument = useCallback(() => docRef.current, []);

  // Get specific Y.js type
  const getYType = useCallback(<T extends Y.AbstractType<any>>(
    field: string
  ): T | null => {
    if (!docRef.current) return null;
    const dataMap = docRef.current.getMap(documentType);
    return dataMap.get(field) as T;
  }, [documentType]);

  // Update field value
  const updateField = useCallback((field: string, value: any) => {
    if (!docRef.current) return;

    const dataMap = docRef.current.getMap(documentType);
    
    docRef.current.transact(() => {
      if (value instanceof Y.AbstractType) {
        dataMap.set(field, value);
      } else if (typeof value === 'string' && (field === 'title' || field === 'description' || field === 'name')) {
        const yText = dataMap.get(field) as Y.Text;
        if (yText) {
          yText.delete(0, yText.length);
          yText.insert(0, value);
        }
      } else if (Array.isArray(value)) {
        const yArray = dataMap.get(field) as Y.Array<any>;
        if (yArray) {
          yArray.delete(0, yArray.length);
          yArray.insert(0, value);
        }
      } else if (typeof value === 'object' && value !== null) {
        const yMap = dataMap.get(field) as Y.Map<any>;
        if (yMap) {
          yMap.forEach((_, key) => yMap.delete(key));
          Object.entries(value).forEach(([k, v]) => yMap.set(k, v));
        }
      } else {
        dataMap.set(field, value);
      }
    }, userId);
  }, [documentType, userId]);

  // Update cursor position
  const updateCursor = useCallback((field: string, position: number, selection?: { start: number; end: number }) => {
    const cursor = { field, position, selection };
    
    setState(prev => ({
      ...prev,
      localUser: { ...prev.localUser, cursor }
    }));

    if (socketRef.current?.connected) {
      socketRef.current.emit('collaboration:cursor', {
        documentId,
        userId,
        cursor
      });
    }
  }, [documentId, userId]);

  // Update awareness
  const updateAwareness = useCallback((awareness: any) => {
    if (socketRef.current?.connected) {
      socketRef.current.emit('collaboration:awareness', {
        documentId,
        userId,
        awareness
      });
    }
  }, [documentId, userId]);

  // Get current value of a field
  const getValue = useCallback((field: string): any => {
    if (!docRef.current) return null;

    const dataMap = docRef.current.getMap(documentType);
    const value = dataMap.get(field);

    if (value instanceof Y.Text) {
      return value.toString();
    } else if (value instanceof Y.Array) {
      return value.toArray();
    } else if (value instanceof Y.Map) {
      const result: Record<string, any> = {};
      value.forEach((v, k) => {
        result[k] = v;
      });
      return result;
    }

    return value;
  }, [documentType]);

  // Auto-connect on mount
  useEffect(() => {
    if (autoConnect) {
      connect();
    }

    return () => {
      if (autoConnect) {
        disconnect();
      }
    };
  }, [autoConnect, connect, disconnect]);

  return {
    // State
    state,
    
    // Connection management
    connect,
    disconnect,
    
    // Document access
    getDocument,
    getYType,
    getValue,
    
    // Update methods
    updateField,
    updateCursor,
    updateAwareness,
    
    // Utility
    isConnected: state.connected,
    activeUsers: state.activeUsers,
    hasUnsavedChanges: state.hasUnsavedChanges
  };
}