import {
  createContext,
  useContext,
  useState,
  useEffect,
  useCallback,
  useRef,
  type ReactNode,
} from "react";
import {
  collaborationService,
  type Collaborator,
  type RoomState,
  type DataChangeMessage,
  type ItemCreatedMessage,
  type ItemDeletedMessage,
  type BatchOperationMessage,
  type SelectionChangedMessage,
  type CursorMovedMessage,
  type ChangeHistoryEntry,
  type SyncRequestMessage,
  type SyncDataMessage,
  type DataSavedMessage,
  type TemplateSyncMessage,
  type UserLeftMessage,
  getOrCreateUserName,
  getColorForUser,
} from "../services/collaboration";

// ==================== TYPES ====================

export interface RemoteCursor {
  connectionId: string;
  userName: string;
  color: string;
  x: number;
  y: number;
  viewId: string | null;
}

export interface CollaborationContextValue {
  // Connection state
  isConnected: boolean;
  isConnecting: boolean;
  currentRoom: string | null;

  // Local user
  localUserName: string;
  localUserColor: string;
  connectionId: string | null;

  // Collaborators
  collaborators: Collaborator[];
  remoteSelections: Map<string, string | null>;
  remoteCursors: Map<string, RemoteCursor>;

  // Change history
  changeHistory: ChangeHistoryEntry[];

  // Room management
  joinRoom: (roomKey: string, metadata?: unknown) => Promise<void>;
  leaveRoom: () => Promise<void>;
  switchRoom: (roomKey: string, metadata?: unknown) => Promise<void>;

  // Data operations (generic)
  sendDataChanged: (
    itemId: string,
    itemType: string,
    changeType: string,
    newValue: unknown,
    fieldPath?: string,
  ) => void;
  sendItemCreated: (
    itemId: string,
    itemType: string,
    item: unknown,
    parentId?: string,
    index?: number,
  ) => void;
  sendItemDeleted: (itemId: string, itemType: string) => void;
  sendBatchOperation: (
    operationType: string,
    itemType: string,
    data: unknown,
  ) => void;

  // Presence
  sendSelectionChanged: (itemId: string | null) => void;
  sendCursorMoved: (x: number, y: number, viewId?: string | null) => void;
  sendViewChanged: (viewId: string) => void;
  sendUserTyping: (itemId: string | null, isTyping: boolean) => void;

  // Sync
  requestSync: () => void;
  sendSync: (targetConnectionId: string, dataJson: string) => void;
  sendDataSaved: () => void;
  broadcastTemplateSync: (templateJson: string, version: number) => void;

  // Event subscriptions for component-specific handlers
  onDataChanged: (callback: (msg: DataChangeMessage) => void) => () => void;
  onItemCreated: (callback: (msg: ItemCreatedMessage) => void) => () => void;
  onItemDeleted: (callback: (msg: ItemDeletedMessage) => void) => () => void;
  onBatchOperation: (
    callback: (msg: BatchOperationMessage) => void,
  ) => () => void;
  onSyncRequested: (callback: (msg: SyncRequestMessage) => void) => () => void;
  onSyncReceived: (callback: (msg: SyncDataMessage) => void) => () => void;
  onDataSaved: (callback: (msg: DataSavedMessage) => void) => () => void;
  onTemplateSync: (callback: (msg: TemplateSyncMessage) => void) => () => void;
}

const CollaborationContext = createContext<CollaborationContextValue | null>(
  null,
);

// ==================== PROVIDER ====================

interface CollaborationProviderProps {
  children: ReactNode;
  /** Auto-connect on mount */
  autoConnect?: boolean;
}

export function CollaborationProvider({
  children,
  autoConnect = true,
}: CollaborationProviderProps) {
  // Connection state
  const [isConnected, setIsConnected] = useState(false);
  const [isConnecting, setIsConnecting] = useState(false);
  const [currentRoom, setCurrentRoom] = useState<string | null>(null);

  // Local user
  const localUserName = useRef(getOrCreateUserName());
  const localUserColor = useRef(getColorForUser(localUserName.current));
  const [connectionId, setConnectionId] = useState<string | null>(null);

  // Collaborators
  const [collaborators, setCollaborators] = useState<Collaborator[]>([]);
  const [remoteSelections, setRemoteSelections] = useState<
    Map<string, string | null>
  >(new Map());
  const [remoteCursors, setRemoteCursors] = useState<Map<string, RemoteCursor>>(
    new Map(),
  );

  // Change history
  const [changeHistory, setChangeHistory] = useState<ChangeHistoryEntry[]>([]);

  // Auto-connect on mount
  // Note: We don't disconnect on unmount because:
  // 1. React Strict Mode double-mounts components, causing connection interruption
  // 2. The collaboration service is a singleton that persists across navigations
  // 3. SignalR has built-in reconnection handling
  useEffect(() => {
    if (autoConnect) {
      collaborationService.connect().catch(() => {
        // Connection errors are logged in the service, ignore here
      });
    }
    // No cleanup - connection persists for the app lifetime
  }, [autoConnect]);

  // Subscribe to connection state changes
  useEffect(() => {
    const unsubscribe = collaborationService.onConnectionStateChanged(
      ({ connected, connecting }) => {
        setIsConnected(connected);
        setIsConnecting(connecting);
        setConnectionId(collaborationService.connectionId);
      },
    );

    return unsubscribe;
  }, []);

  // Subscribe to room state (on join)
  useEffect(() => {
    const unsubscribe = collaborationService.onRoomState((state: RoomState) => {
      setCurrentRoom(state.roomKey);
      setCollaborators(state.collaborators);

      // Initialize remote selections and cursors
      const selections = new Map<string, string | null>();
      const cursors = new Map<string, RemoteCursor>();

      state.collaborators.forEach((c) => {
        selections.set(c.connectionId, c.selectedItemId);
        if (c.cursorX != null && c.cursorY != null) {
          cursors.set(c.connectionId, {
            connectionId: c.connectionId,
            userName: c.userName,
            color: c.color,
            x: c.cursorX,
            y: c.cursorY,
            viewId: c.currentViewId,
          });
        }
      });

      setRemoteSelections(selections);
      setRemoteCursors(cursors);
    });

    return unsubscribe;
  }, []);

  // Subscribe to user join/leave
  useEffect(() => {
    const unsubJoin = collaborationService.onUserJoined(
      (collaborator: Collaborator) => {
        setCollaborators((prev) => {
          if (prev.some((c) => c.connectionId === collaborator.connectionId)) {
            return prev;
          }
          return [...prev, collaborator];
        });
      },
    );

    const unsubLeave = collaborationService.onUserLeft(
      (message: UserLeftMessage) => {
        setCollaborators((prev) =>
          prev.filter((c) => c.connectionId !== message.connectionId),
        );
        setRemoteSelections((prev) => {
          const newMap = new Map(prev);
          newMap.delete(message.connectionId);
          return newMap;
        });
        setRemoteCursors((prev) => {
          const newMap = new Map(prev);
          newMap.delete(message.connectionId);
          return newMap;
        });
      },
    );

    return () => {
      unsubJoin();
      unsubLeave();
    };
  }, []);

  // Subscribe to selection changes
  useEffect(() => {
    const unsubscribe = collaborationService.onSelectionChanged(
      (message: SelectionChangedMessage) => {
        setRemoteSelections((prev) => {
          const newMap = new Map(prev);
          newMap.set(message.connectionId, message.itemId);
          return newMap;
        });
      },
    );

    return unsubscribe;
  }, []);

  // Subscribe to cursor movements
  useEffect(() => {
    const unsubscribe = collaborationService.onCursorMoved(
      (message: CursorMovedMessage) => {
        const collaborator = collaborationService.getCollaborator(
          message.connectionId,
        );

        setRemoteCursors((prev) => {
          const newMap = new Map(prev);
          newMap.set(message.connectionId, {
            connectionId: message.connectionId,
            userName: collaborator?.userName || "Utente",
            color: collaborator?.color || "#888",
            x: message.x,
            y: message.y,
            viewId: message.viewId,
          });
          return newMap;
        });
      },
    );

    return unsubscribe;
  }, []);

  // Subscribe to change history
  useEffect(() => {
    const unsubscribe = collaborationService.onChangeHistoryUpdated(
      (history) => {
        setChangeHistory(history);
      },
    );

    return unsubscribe;
  }, []);

  // Room management callbacks
  const joinRoom = useCallback(async (roomKey: string, metadata?: unknown) => {
    await collaborationService.joinRoom(
      roomKey,
      localUserName.current,
      metadata,
    );
    setCurrentRoom(roomKey);
  }, []);

  const leaveRoom = useCallback(async () => {
    await collaborationService.leaveRoom();
    setCurrentRoom(null);
    setCollaborators([]);
    setRemoteSelections(new Map());
    setRemoteCursors(new Map());
  }, []);

  const switchRoom = useCallback(
    async (roomKey: string, metadata?: unknown) => {
      await collaborationService.switchRoom(roomKey, metadata);
      setCurrentRoom(roomKey);
      setCollaborators([]);
      setRemoteSelections(new Map());
      setRemoteCursors(new Map());
    },
    [],
  );

  // Data operation callbacks
  const sendDataChanged = useCallback(
    (
      itemId: string,
      itemType: string,
      changeType: string,
      newValue: unknown,
      fieldPath?: string,
    ) => {
      collaborationService.sendDataChanged(
        itemId,
        itemType,
        changeType,
        newValue,
        fieldPath,
      );
    },
    [],
  );

  const sendItemCreated = useCallback(
    (
      itemId: string,
      itemType: string,
      item: unknown,
      parentId?: string,
      index?: number,
    ) => {
      collaborationService.sendItemCreated(
        itemId,
        itemType,
        item,
        parentId,
        index,
      );
    },
    [],
  );

  const sendItemDeleted = useCallback((itemId: string, itemType: string) => {
    collaborationService.sendItemDeleted(itemId, itemType);
  }, []);

  const sendBatchOperation = useCallback(
    (operationType: string, itemType: string, data: unknown) => {
      collaborationService.sendBatchOperation(operationType, itemType, data);
    },
    [],
  );

  // Presence callbacks
  const sendSelectionChanged = useCallback((itemId: string | null) => {
    collaborationService.sendSelectionChanged(itemId);
  }, []);

  const sendCursorMoved = useCallback(
    (x: number, y: number, viewId?: string | null) => {
      collaborationService.sendCursorMoved(x, y, viewId);
    },
    [],
  );

  const sendViewChanged = useCallback((viewId: string) => {
    collaborationService.sendViewChanged(viewId);
  }, []);

  const sendUserTyping = useCallback(
    (itemId: string | null, isTyping: boolean) => {
      collaborationService.sendUserTyping(itemId, isTyping);
    },
    [],
  );

  // Sync callbacks
  const requestSync = useCallback(() => {
    collaborationService.requestSync();
  }, []);

  const sendSync = useCallback(
    (targetConnectionId: string, dataJson: string) => {
      collaborationService.sendSync(targetConnectionId, dataJson);
    },
    [],
  );

  const sendDataSaved = useCallback(() => {
    collaborationService.sendDataSaved();
  }, []);

  const broadcastTemplateSync = useCallback(
    (templateJson: string, version: number) => {
      collaborationService.broadcastTemplateSync(templateJson, version);
    },
    [],
  );

  // Event subscription pass-through
  const onDataChanged = useCallback(
    (callback: (msg: DataChangeMessage) => void) => {
      return collaborationService.onDataChanged(callback);
    },
    [],
  );

  const onItemCreated = useCallback(
    (callback: (msg: ItemCreatedMessage) => void) => {
      return collaborationService.onItemCreated(callback);
    },
    [],
  );

  const onItemDeleted = useCallback(
    (callback: (msg: ItemDeletedMessage) => void) => {
      return collaborationService.onItemDeleted(callback);
    },
    [],
  );

  const onBatchOperation = useCallback(
    (callback: (msg: BatchOperationMessage) => void) => {
      return collaborationService.onBatchOperation(callback);
    },
    [],
  );

  const onSyncRequested = useCallback(
    (callback: (msg: SyncRequestMessage) => void) => {
      return collaborationService.onSyncRequested(callback);
    },
    [],
  );

  const onSyncReceived = useCallback(
    (callback: (msg: SyncDataMessage) => void) => {
      return collaborationService.onSyncReceived(callback);
    },
    [],
  );

  const onDataSaved = useCallback(
    (callback: (msg: DataSavedMessage) => void) => {
      return collaborationService.onDataSaved(callback);
    },
    [],
  );

  const onTemplateSync = useCallback(
    (callback: (msg: TemplateSyncMessage) => void) => {
      return collaborationService.onTemplateSync(callback);
    },
    [],
  );

  const value: CollaborationContextValue = {
    // Connection state
    isConnected,
    isConnecting,
    currentRoom,

    // Local user
    localUserName: localUserName.current,
    localUserColor: localUserColor.current,
    connectionId,

    // Collaborators
    collaborators,
    remoteSelections,
    remoteCursors,

    // Change history
    changeHistory,

    // Room management
    joinRoom,
    leaveRoom,
    switchRoom,

    // Data operations
    sendDataChanged,
    sendItemCreated,
    sendItemDeleted,
    sendBatchOperation,

    // Presence
    sendSelectionChanged,
    sendCursorMoved,
    sendViewChanged,
    sendUserTyping,

    // Sync
    requestSync,
    sendSync,
    sendDataSaved,
    broadcastTemplateSync,

    // Event subscriptions
    onDataChanged,
    onItemCreated,
    onItemDeleted,
    onBatchOperation,
    onSyncRequested,
    onSyncReceived,
    onDataSaved,
    onTemplateSync,
  };

  return (
    <CollaborationContext.Provider value={value}>
      {children}
    </CollaborationContext.Provider>
  );
}

// ==================== HOOKS ====================

/**
 * Hook to access the collaboration context
 */
export function useCollaboration(): CollaborationContextValue {
  const context = useContext(CollaborationContext);
  if (!context) {
    throw new Error(
      "useCollaboration must be used within a CollaborationProvider",
    );
  }
  return context;
}

/**
 * Hook to automatically join a room when a component mounts
 */
export function useCollaborationRoom(
  roomKey: string | null,
  options?: {
    metadata?: unknown;
    enabled?: boolean;
  },
): CollaborationContextValue {
  const collaboration = useCollaboration();
  const { enabled = true, metadata } = options || {};

  // Use refs to avoid dependency changes causing re-runs
  const joinRoomRef = useRef(collaboration.joinRoom);
  const leaveRoomRef = useRef(collaboration.leaveRoom);
  const metadataRef = useRef(metadata);

  // Keep refs updated
  joinRoomRef.current = collaboration.joinRoom;
  leaveRoomRef.current = collaboration.leaveRoom;
  metadataRef.current = metadata;

  // Track if we successfully joined a room
  const joinedRoomRef = useRef<string | null>(null);

  useEffect(() => {
    if (!enabled || !roomKey) return;

    // Use a flag to prevent race conditions
    let cancelled = false;

    const doJoin = async () => {
      try {
        if (!cancelled) {
          await joinRoomRef.current(roomKey, metadataRef.current);
          if (!cancelled) {
            joinedRoomRef.current = roomKey;
          }
        }
      } catch (error) {
        if (!cancelled) {
          console.error("[useCollaborationRoom] Failed to join room:", error);
        }
      }
    };

    doJoin();

    return () => {
      cancelled = true;
      // Only leave if we actually joined this room
      if (joinedRoomRef.current === roomKey) {
        joinedRoomRef.current = null;
        leaveRoomRef.current().catch((err) => {
          // Silently ignore leave errors - connection may already be closed
          console.log(
            "[useCollaborationRoom] Leave room:",
            err?.message || "ok",
          );
        });
      }
    };
  }, [roomKey, enabled]); // Only re-run when roomKey or enabled changes

  return collaboration;
}

/**
 * Hook to get cursors filtered by current view
 */
export function useRemoteCursorsForView(viewId: string | null): RemoteCursor[] {
  const { remoteCursors } = useCollaboration();

  return Array.from(remoteCursors.values()).filter(
    (cursor) => viewId === null || cursor.viewId === viewId,
  );
}

/**
 * Hook to check if an item is selected by a remote user
 */
export function useRemoteSelection(itemId: string): {
  isSelected: boolean;
  selectedBy: Collaborator | null;
} {
  const { remoteSelections, collaborators } = useCollaboration();

  for (const [connectionId, selectedId] of remoteSelections.entries()) {
    if (selectedId === itemId) {
      const collaborator =
        collaborators.find((c) => c.connectionId === connectionId) || null;
      return { isSelected: true, selectedBy: collaborator };
    }
  }

  return { isSelected: false, selectedBy: null };
}
