import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import {
  ReactFlow,
  MiniMap,
  Controls,
  Background,
  BackgroundVariant,
  useNodesState,
  useEdgesState,
  addEdge,
  Connection,
  Edge,
  Node,
  NodeTypes,
  EdgeTypes,
  OnConnect,
  OnEdgesDelete,
  useReactFlow,
  ReactFlowProvider,
  NodeChange,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';

import TeamFlowNode, { TeamNodeData } from './TeamFlowNode';
import StaffFlowNode, { StaffNodeData } from './StaffFlowNode';
import RoleFlowNode, { RoleNodeData } from './RoleFlowNode';
import CustomEdge, { CustomEdgeData } from './CustomEdge';
import { Team, SystemUser, SystemRole } from '../../types/permissions';

// Node types registration - must be defined outside component to avoid re-creation
const nodeTypes: NodeTypes = {
  team: TeamFlowNode,
  staff: StaffFlowNode,
  role: RoleFlowNode,
};

// Edge types registration
const edgeTypes: EdgeTypes = {
  custom: CustomEdge,
};

export interface TeamGraphProps {
  teams: Team[];
  users: SystemUser[];
  roles?: SystemRole[];
  loading?: boolean;
  nodePositions?: Record<string, { x: number; y: number }>;
  showRoles?: boolean;
  onAddMemberToTeam?: (teamId: number, userId: number) => Promise<void>;
  onRemoveMemberFromTeam?: (teamId: number, userId: number) => Promise<void>;
  onConnectTeamToRole?: (teamId: number, roleId: number) => Promise<void>;
  onDisconnectTeamFromRole?: (teamId: number, roleId: number) => Promise<void>;
  onNodePositionChange?: (nodeId: string, position: { x: number; y: number }) => void;
  onEditTeam?: (team: Team) => void;
  onDeleteTeam?: (team: Team) => void;
  onAddMember?: (team: Team) => void;
  onDropUserOnTeam?: (teamId: number, userId: number) => Promise<void>;
  showMinimap?: boolean;
  showControls?: boolean;
}

// Inner component that uses React Flow hooks
const TeamGraphInner: React.FC<TeamGraphProps> = ({
  teams,
  users,
  roles = [],
  loading = false,
  nodePositions = {},
  showRoles = false,
  onAddMemberToTeam,
  onRemoveMemberFromTeam,
  onConnectTeamToRole,
  onDisconnectTeamFromRole,
  onNodePositionChange,
  onEditTeam,
  onDeleteTeam,
  onAddMember,
  onDropUserOnTeam,
  showMinimap = true,
  showControls = true,
}) => {
  const reactFlowWrapper = useRef<HTMLDivElement>(null);
  const { fitView, screenToFlowPosition } = useReactFlow();
  const initialFitDone = useRef(false);

  // Store all callbacks in a single ref to avoid re-renders
  const callbacksRef = useRef({
    onEditTeam,
    onDeleteTeam,
    onAddMember,
    onRemoveMemberFromTeam,
    onDisconnectTeamFromRole,
    onAddMemberToTeam,
    onConnectTeamToRole,
    onNodePositionChange,
    onDropUserOnTeam,
  });

  // Update ref synchronously (no useEffect needed - this runs during render)
  callbacksRef.current = {
    onEditTeam,
    onDeleteTeam,
    onAddMember,
    onRemoveMemberFromTeam,
    onDisconnectTeamFromRole,
    onAddMemberToTeam,
    onConnectTeamToRole,
    onNodePositionChange,
    onDropUserOnTeam,
  };

  // Store nodePositions in ref
  const nodePositionsRef = useRef(nodePositions);
  nodePositionsRef.current = nodePositions;

  // Compute nodes using useMemo - only recompute when actual data changes
  const computedNodes = useMemo(() => {
    const nodesArray: Node[] = [];

    // Find assigned user IDs
    const assignedUserIds = new Set<number>();
    teams.forEach((team) => {
      team.members?.forEach((member) => {
        assignedUserIds.add(member.usr_id);
      });
    });

    // Unassigned users (left side)
    const unassignedUsers = users.filter((u) => !assignedUserIds.has(u.usr_id));
    unassignedUsers.forEach((user, index) => {
      const nodeId = `staff-${user.usr_id}`;
      const savedPos = nodePositionsRef.current[nodeId];
      nodesArray.push({
        id: nodeId,
        type: 'staff',
        position: savedPos || { x: 50, y: index * 80 + 50 },
        data: {
          user,
          showTeams: true,
          showRoles: false,
        } as StaffNodeData,
      });
    });

    // Teams (center)
    teams.forEach((team, index) => {
      const nodeId = `team-${team.team_id}`;
      const savedPos = nodePositionsRef.current[nodeId];
      nodesArray.push({
        id: nodeId,
        type: 'team',
        position: savedPos || { x: 350, y: index * 200 + 50 },
        data: {
          team,
          onEdit: (t: Team) => callbacksRef.current.onEditTeam?.(t),
          onDelete: (t: Team) => callbacksRef.current.onDeleteTeam?.(t),
          onAddMember: (t: Team) => callbacksRef.current.onAddMember?.(t),
        } as TeamNodeData,
      });
    });

    // Roles (right side) - if showing
    if (showRoles && roles.length > 0) {
      roles.forEach((role, index) => {
        const nodeId = `role-${role.role_id}`;
        const savedPos = nodePositionsRef.current[nodeId];
        nodesArray.push({
          id: nodeId,
          type: 'role',
          position: savedPos || { x: 700, y: index * 180 + 50 },
          data: {
            role,
          } as RoleNodeData,
        });
      });
    }

    return nodesArray;
  }, [teams, users, roles, showRoles]);

  // Compute edges using useMemo
  const computedEdges = useMemo(() => {
    const edgesArray: Edge[] = [];

    teams.forEach((team) => {
      // Staff -> Team edges
      team.members?.forEach((member) => {
        const edgeId = `edge-staff-${member.usr_id}-team-${team.team_id}`;
        edgesArray.push({
          id: edgeId,
          source: `staff-${member.usr_id}`,
          target: `team-${team.team_id}`,
          sourceHandle: 'output',
          targetHandle: 'input',
          type: 'custom',
          data: {
            color: member.pivot?.tm_is_lead ? '#ffc107' : team.team_color,
            label: member.pivot?.tm_is_lead ? 'Lead' : undefined,
            deletable: true,
            onDelete: () => callbacksRef.current.onRemoveMemberFromTeam?.(team.team_id, member.usr_id),
          } as CustomEdgeData,
        });
      });

      // Team -> Role edges
      if (showRoles && team.roles) {
        team.roles.forEach((role) => {
          const edgeId = `edge-team-${team.team_id}-role-${role.role_id}`;
          edgesArray.push({
            id: edgeId,
            source: `team-${team.team_id}`,
            target: `role-${role.role_id}`,
            sourceHandle: 'output',
            targetHandle: 'input',
            type: 'custom',
            data: {
              color: role.role_color,
              deletable: true,
              onDelete: () => callbacksRef.current.onDisconnectTeamFromRole?.(team.team_id, role.role_id),
            } as CustomEdgeData,
          });
        });
      }
    });

    return edgesArray;
  }, [teams, showRoles]);

  // Use React Flow's state hooks - initialize with empty arrays
  const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);

  // Track previous computed values to detect actual changes
  const prevComputedRef = useRef<{ nodes: Node[]; edges: Edge[] }>({ nodes: [], edges: [] });

  // Update nodes/edges only when the computed values actually change
  // Use a stringified comparison to avoid infinite loops from object reference changes
  useEffect(() => {
    // Create a simplified key for comparison
    const nodesKey = computedNodes.map((n) => n.id).join(',');
    const edgesKey = computedEdges.map((e) => e.id).join(',');
    const prevNodesKey = prevComputedRef.current.nodes.map((n) => n.id).join(',');
    const prevEdgesKey = prevComputedRef.current.edges.map((e) => e.id).join(',');

    if (nodesKey !== prevNodesKey || edgesKey !== prevEdgesKey) {
      prevComputedRef.current = { nodes: computedNodes, edges: computedEdges };
      setNodes(computedNodes);
      setEdges(computedEdges);
    }
  }, [computedNodes, computedEdges, setNodes, setEdges]);

  // Handle new connections
  const handleConnect: OnConnect = useCallback(
    async (connection: Connection) => {
      if (!connection.source || !connection.target) return;

      // Staff -> Team connection
      const staffMatch = connection.source.match(/^staff-(\d+)$/);
      const teamMatch = connection.target.match(/^team-(\d+)$/);

      if (staffMatch && teamMatch) {
        const userId = parseInt(staffMatch[1], 10);
        const teamId = parseInt(teamMatch[1], 10);

        const fn = callbacksRef.current.onAddMemberToTeam;
        if (fn) {
          try {
            await fn(teamId, userId);
          } catch (error) {
            console.error('Failed to add member to team:', error);
          }
        } else {
          setEdges((eds) => addEdge({ ...connection, type: 'custom' }, eds));
        }
        return;
      }

      // Team -> Role connection
      const teamSourceMatch = connection.source.match(/^team-(\d+)$/);
      const roleMatch = connection.target.match(/^role-(\d+)$/);

      if (teamSourceMatch && roleMatch) {
        const teamId = parseInt(teamSourceMatch[1], 10);
        const roleId = parseInt(roleMatch[1], 10);

        const fn = callbacksRef.current.onConnectTeamToRole;
        if (fn) {
          try {
            await fn(teamId, roleId);
          } catch (error) {
            console.error('Failed to connect team to role:', error);
          }
        } else {
          setEdges((eds) => addEdge({ ...connection, type: 'custom' }, eds));
        }
        return;
      }
    },
    [setEdges]
  );

  // Handle edge deletion
  const handleEdgesDelete: OnEdgesDelete = useCallback(async (deletedEdges: Edge[]) => {
    for (const edge of deletedEdges) {
      // Staff -> Team edge deletion
      const staffMatch = edge.source.match(/^staff-(\d+)$/);
      const teamMatch = edge.target.match(/^team-(\d+)$/);

      if (staffMatch && teamMatch) {
        const userId = parseInt(staffMatch[1], 10);
        const teamId = parseInt(teamMatch[1], 10);
        const fn = callbacksRef.current.onRemoveMemberFromTeam;
        if (fn) {
          try {
            await fn(teamId, userId);
          } catch (error) {
            console.error('Failed to remove member from team:', error);
          }
        }
        continue;
      }

      // Team -> Role edge deletion
      const teamSourceMatch = edge.source.match(/^team-(\d+)$/);
      const roleMatch = edge.target.match(/^role-(\d+)$/);

      if (teamSourceMatch && roleMatch) {
        const teamId = parseInt(teamSourceMatch[1], 10);
        const roleId = parseInt(roleMatch[1], 10);
        const fn = callbacksRef.current.onDisconnectTeamFromRole;
        if (fn) {
          try {
            await fn(teamId, roleId);
          } catch (error) {
            console.error('Failed to disconnect team from role:', error);
          }
        }
      }
    }
  }, []);

  // Handle node position changes
  const handleNodesChange = useCallback(
    (changes: NodeChange[]) => {
      onNodesChange(changes);

      changes.forEach((change) => {
        if (change.type === 'position' && change.dragging === false && change.position) {
          const fn = callbacksRef.current.onNodePositionChange;
          if (fn) {
            fn(change.id, change.position);
          }
        }
      });
    },
    [onNodesChange]
  );

  // Fit view on initial load only
  useEffect(() => {
    if (!initialFitDone.current && nodes.length > 0) {
      initialFitDone.current = true;
      setTimeout(() => fitView({ padding: 0.1 }), 100);
    }
  }, [nodes.length, fitView]);

  // Handle drag over for external drops
  const handleDragOver = useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  // Handle drop from sidebar
  const handleDrop = useCallback(
    async (event: React.DragEvent) => {
      event.preventDefault();

      try {
        const data = JSON.parse(event.dataTransfer.getData('application/json'));

        if (data.type === 'staff' && data.userId) {
          // Get drop position in flow coordinates
          const position = screenToFlowPosition({
            x: event.clientX,
            y: event.clientY,
          });

          // Check if dropped on a team node
          const teamNode = nodes.find((node) => {
            if (node.type !== 'team') return false;
            const nodeX = node.position.x;
            const nodeY = node.position.y;
            // Team nodes are approximately 200x150
            const nodeWidth = 200;
            const nodeHeight = 150;
            return (
              position.x >= nodeX &&
              position.x <= nodeX + nodeWidth &&
              position.y >= nodeY &&
              position.y <= nodeY + nodeHeight
            );
          });

          if (teamNode) {
            // Dropped on a team - add member to that team
            const teamId = parseInt(teamNode.id.replace('team-', ''), 10);
            const fn = callbacksRef.current.onDropUserOnTeam || callbacksRef.current.onAddMemberToTeam;
            if (fn) {
              await fn(teamId, data.userId);
            }
          }
        }
      } catch (err) {
        console.error('Failed to handle drop:', err);
      }
    },
    [nodes, screenToFlowPosition]
  );

  if (loading) {
    return (
      <Box
        sx={{
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          height: '100%',
          minHeight: 400,
        }}
      >
        <CircularProgress />
      </Box>
    );
  }

  return (
    <Box
      ref={reactFlowWrapper}
      onDragOver={handleDragOver}
      onDrop={handleDrop}
      sx={{
        width: '100%',
        height: '100%',
        minHeight: 500,
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        zIndex: 1,
      }}
    >
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={handleNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={handleConnect}
        onEdgesDelete={handleEdgesDelete}
        nodeTypes={nodeTypes}
        edgeTypes={edgeTypes}
        fitView
        fitViewOptions={{ padding: 0.1 }}
        defaultEdgeOptions={{
          type: 'custom',
          animated: false,
        }}
        connectionLineStyle={{ stroke: '#4caf50', strokeWidth: 2 }}
        snapToGrid
        snapGrid={[15, 15]}
        deleteKeyCode={['Backspace', 'Delete']}
        multiSelectionKeyCode={['Shift']}
        panOnScroll
        selectionOnDrag
        panOnDrag={[1, 2]}
        selectNodesOnDrag={false}
      >
        <Background variant={BackgroundVariant.Dots} gap={20} size={1} />
        {showControls && <Controls />}
        {showMinimap && (
          <MiniMap
            nodeColor={(node) => {
              if (node.type === 'team') {
                return (node.data as TeamNodeData).team.team_color || '#1976d2';
              }
              if (node.type === 'role') {
                return (node.data as RoleNodeData).role.role_color || '#9c27b0';
              }
              return '#607d8b';
            }}
            maskColor="rgba(0,0,0,0.1)"
            style={{ background: '#f5f5f5' }}
          />
        )}
      </ReactFlow>
    </Box>
  );
};

// Wrapper component with ReactFlowProvider
const TeamGraph: React.FC<TeamGraphProps> = (props) => {
  return (
    <ReactFlowProvider>
      <TeamGraphInner {...props} />
    </ReactFlowProvider>
  );
};

export default TeamGraph;
