// contexts/WindowManagerContext.tsx

'use client';

import React, { createContext, useContext, useState, useEffect } from 'react';

type WindowProps = {
  width?: string;
  height?: string;
  initialState?: 'open' | 'closed' | 'minimized';
  gravity?: number; // New gravity property
};

type WindowData = {
  name: string;
  Component: React.ComponentType;
  windowProps: WindowProps;
};

type WindowState = {
  visible: boolean;
  minimized: boolean;
  maximized: boolean;
  width: number;
  height: number;
  x: number;
  y: number;
  zIndex: number;
  prevWidth?: number;
  prevHeight?: number;
  prevX?: number;
  prevY?: number;
};

type WindowManagerContextType = {
  windows: { [key: string]: WindowData };
  windowStates: { [key: string]: WindowState };
  openWindow: (name: string) => void;
  closeWindow: (name: string) => void;
  minimizeWindow: (name: string) => void;
  restoreWindow: (name: string) => void;
  maximizeWindow: (name: string) => void;
  updateWindowState: (name: string, newState: Partial<WindowState>) => void;
  bringToFront: (name: string) => void;
};

const WindowManagerContext = createContext<WindowManagerContextType | undefined>(undefined);

export const useWindowManager = () => {
  const context = useContext(WindowManagerContext);
  if (!context) {
    throw new Error('useWindowManager must be used within a WindowManagerProvider');
  }
  return context;
};

export const WindowManagerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [windows, setWindows] = useState<{ [key: string]: WindowData }>({});
  const [windowStates, setWindowStates] = useState<{ [key: string]: WindowState }>({});
  const [nextZIndex, setNextZIndex] = useState(1);

  // Define grid size constant
  const GRID_SIZE = 25;

  type WindowDataAndState = {
    windowData: WindowData;
    windowState: WindowState;
  };

  useEffect(() => {
    // Dynamically import window components
    const importWindows = async () => {
      const windowsContext = require.context('../windows', false, /\.tsx$/);
      const windowFiles = windowsContext.keys();

      let zIndexCounter = 1;

      const windowsDataPromises: Promise<WindowDataAndState>[] = windowFiles.map(
        async (file: string, index: number): Promise<WindowDataAndState> => {
          const mod = await windowsContext(file);
          const name = file.replace('./', '').replace('.tsx', '');
          const Component = mod.default;
          const windowProps = mod.windowProps || {};

          // Parse initial size
          let width = windowProps.width
            ? parseSize(windowProps.width, 'width')
            : 0.35 * window.innerWidth;
          let height = windowProps.height
            ? parseSize(windowProps.height, 'height')
            : 0.2 * window.innerHeight;

          // Adjust width and height to align with grid
          width = Math.round(width / GRID_SIZE) * GRID_SIZE;
          height = Math.round(height / GRID_SIZE) * GRID_SIZE;

          // Determine initial state
          const initialState = windowProps.initialState || 'open';
          let visible = true;
          let minimized = false;

          switch (initialState) {
            case 'open':
              visible = true;
              minimized = false;
              break;
            case 'closed':
              visible = false;
              minimized = false;
              break;
            case 'minimized':
              visible = false;
              minimized = true;
              break;
            default:
              visible = true;
              minimized = false;
              break;
          }

          // Adjust initial positions to align with grid
          const initialX = Math.round((100 + index * 50) / GRID_SIZE) * GRID_SIZE;
          const initialY = Math.round((100 + index * 50) / GRID_SIZE) * GRID_SIZE;

          const windowData: WindowData = { name, Component, windowProps };
          const windowState: WindowState = {
            visible,
            minimized,
            maximized: false,
            width,
            height,
            x: initialX,
            y: initialY,
            zIndex: zIndexCounter++,
          };

          return { windowData, windowState };
        }
      );

      const windowsDataArray: WindowDataAndState[] = await Promise.all(windowsDataPromises);

      const newWindows: { [key: string]: WindowData } = {};
      const newWindowStates: { [key: string]: WindowState } = {};

      windowsDataArray.forEach(({ windowData, windowState }) => {
        newWindows[windowData.name] = windowData;
        newWindowStates[windowData.name] = windowState;
      });

      setWindows(newWindows);
      setWindowStates(newWindowStates);
      setNextZIndex(zIndexCounter);
    };

    importWindows();
  }, []);

  const bringToFront = (name: string) => {
    setWindowStates((prev) => {
      const maxZ = nextZIndex;
      setNextZIndex(maxZ + 1);
      return {
        ...prev,
        [name]: {
          ...prev[name],
          zIndex: maxZ,
        },
      };
    });
  };

  const openWindow = (name: string) => {
    setWindowStates((prev) => ({
      ...prev,
      [name]: {
        ...prev[name],
        visible: true,
        minimized: false,
      },
    }));
    bringToFront(name);
  };

  const closeWindow = (name: string) => {
    setWindowStates((prev) => ({
      ...prev,
      [name]: {
        ...prev[name],
        visible: false,
        minimized: false,
      },
    }));
  };

  const minimizeWindow = (name: string) => {
    setWindowStates((prev) => ({
      ...prev,
      [name]: {
        ...prev[name],
        minimized: true,
        visible: false,
      },
    }));
  };

  const restoreWindow = (name: string) => {
    setWindowStates((prev) => ({
      ...prev,
      [name]: {
        ...prev[name],
        minimized: false,
        visible: true,
      },
    }));
    bringToFront(name);
  };

  const maximizeWindow = (name: string) => {
    setWindowStates((prev) => {
      const state = prev[name];
      const isMaximized = !state.maximized;

      if (isMaximized) {
        // Store previous size and position
        const prevSizePosition = {
          prevWidth: state.width,
          prevHeight: state.height,
          prevX: state.x,
          prevY: state.y,
        };

        const maxWidth = window.innerWidth - 20; // Adjusted width for spacing
        const maxHeight = window.innerHeight - 20; // Adjusted height for spacing

        // Adjust to align with grid
        const adjustedWidth = Math.floor(maxWidth / GRID_SIZE) * GRID_SIZE;
        const adjustedHeight = Math.floor(maxHeight / GRID_SIZE) * GRID_SIZE;

        return {
          ...prev,
          [name]: {
            ...state,
            maximized: true,
            ...prevSizePosition,
            x: 10, // Adjusted for spacing when maximized
            y: 10, // Adjusted for spacing when maximized
            width: adjustedWidth,
            height: adjustedHeight,
          },
        };
      } else {
        // Restore previous size and position
        return {
          ...prev,
          [name]: {
            ...state,
            maximized: false,
            width: state.prevWidth || state.width,
            height: state.prevHeight || state.height,
            x: state.prevX || state.x,
            y: state.prevY || state.y,
          },
        };
      }
    });
    bringToFront(name);
  };

  const updateWindowState = (name: string, newState: Partial<WindowState>) => {
    setWindowStates((prev) => {
      const updatedState = {
        ...prev[name],
        ...newState,
      };

      // Ensure positions and sizes align with the grid
      updatedState.x = Math.round(updatedState.x / GRID_SIZE) * GRID_SIZE;
      updatedState.y = Math.round(updatedState.y / GRID_SIZE) * GRID_SIZE;
      updatedState.width = Math.round(updatedState.width / GRID_SIZE) * GRID_SIZE;
      updatedState.height = Math.round(updatedState.height / GRID_SIZE) * GRID_SIZE;

      return {
        ...prev,
        [name]: updatedState,
      };
    });
  };

  function parseSize(size: string, dimension: 'width' | 'height'): number {
    if (typeof window === 'undefined') return 400;
    if (size.endsWith('%')) {
      const percentage = parseFloat(size) / 100;
      if (dimension === 'width') {
        return percentage * window.innerWidth;
      } else {
        return percentage * window.innerHeight;
      }
    } else if (size.endsWith('px')) {
      return parseFloat(size);
    } else {
      return dimension === 'width' ? 400 : 300;
    }
  }

  return (
    <WindowManagerContext.Provider
      value={{
        windows,
        windowStates,
        openWindow,
        closeWindow,
        minimizeWindow,
        restoreWindow,
        maximizeWindow,
        updateWindowState,
        bringToFront,
      }}
    >
      {children}
    </WindowManagerContext.Provider>
  );
};

AND:

// app/DraggableWindows.tsx

'use client';

import React from 'react';
import { Rnd } from 'react-rnd';
import {
  AiOutlineClose,
  AiOutlineMinus,
  AiOutlineFullscreen,
} from 'react-icons/ai';
import { useWindowManager } from '../contexts/WindowManagerContext';
import { motion, AnimatePresence } from 'framer-motion';

const DraggableWindows: React.FC = () => {
  const {
    windows,
    windowStates,
    closeWindow,
    minimizeWindow,
    maximizeWindow,
    updateWindowState,
    bringToFront,
  } = useWindowManager();

  return (
    <div className="w-full h-full fixed top-0 left-0">
      {Object.keys(windows).map((name) => {
        const { Component } = windows[name];
        const state = windowStates[name];

        if (!Component) return null;

        const isVisible = state.visible && !state.minimized;

        return (
          <AnimatePresence key={name}>
            {isVisible && (
              <WindowWrapper
                key={name}
                name={name}
                state={state}
                bringToFront={bringToFront}
                updateWindowState={updateWindowState}
                minimizeWindow={minimizeWindow}
                maximizeWindow={maximizeWindow}
                closeWindow={closeWindow}
              >
                <Component />
              </WindowWrapper>
            )}
          </AnimatePresence>
        );
      })}
    </div>
  );
};

interface WindowState {
  visible: boolean;
  minimized: boolean;
  maximized: boolean;
  width: number;
  height: number;
  x: number;
  y: number;
  zIndex: number;
}

interface WindowWrapperProps {
  name: string;
  state: WindowState;
  bringToFront: (name: string) => void;
  updateWindowState: (name: string, newState: Partial<WindowState>) => void;
  minimizeWindow: (name: string) => void;
  maximizeWindow: (name: string) => void;
  closeWindow: (name: string) => void;
  children: React.ReactNode;
}

const WindowWrapper: React.FC<WindowWrapperProps> = ({
  name,
  state,
  bringToFront,
  updateWindowState,
  minimizeWindow,
  maximizeWindow,
  closeWindow,
  children,
}) => {
  const { windowStates } = useWindowManager();

  // Determine if the window is the topmost window
  const maxZIndex = Math.max(...Object.values(windowStates).map((w) => w.zIndex));
  const isTopWindow = state.zIndex === maxZIndex;

  // Determine box shadow based on zIndex
  const boxShadow = isTopWindow
    ? '0px 0px 10px rgba(0,0,0,0.5)'
    : '0px 0px 5px rgba(0,0,0,0.2)';

  // Use layout animations for smooth transitions
  const windowMotionProps = {
    layout: true,
    initial: { opacity: 0 },
    animate: { opacity: 1 },
    exit: { opacity: 0 },
    transition: { duration: 0.2 },
  };

  return (
    <Rnd
      size={{ width: state.width, height: state.height }}
      position={{ x: state.x, y: state.y }}
      onMouseDown={() => bringToFront(name)}
      onDragStart={() => bringToFront(name)}
      onResizeStart={() => bringToFront(name)}
      onDragStop={(e, d) => {
        updateWindowState(name, { x: d.x, y: d.y });
      }}
      onResizeStop={(e, direction, ref, delta, position) => {
        const newWidth = parseInt(ref.style.width, 10);
        const newHeight = parseInt(ref.style.height, 10);
        updateWindowState(name, {
          width: newWidth,
          height: newHeight,
          x: position.x,
          y: position.y,
        });
      }}
      minWidth={200}
      minHeight={100}
      grid={[25, 25]} // Snapping to 25px grid
      dragHandleClassName="window-bar" // Specify draggable area
      // Removed 'cancel' prop
      disableDragging={state.maximized}
      enableResizing={!state.maximized}
      bounds="parent"
      style={{ zIndex: state.zIndex }}
    >
      <motion.div
        {...windowMotionProps}
        className="bg-white border border-gray-500 flex flex-col h-full"
        style={{ boxShadow }}
        onMouseDown={() => bringToFront(name)}
      >
        {/* Window Bar */}
        <div
          className="flex items-center justify-between bg-gray-200 p-1 window-bar" // Added 'window-bar' class
          style={{ cursor: state.maximized ? 'default' : 'move' }}
        >
          <span className="pl-2">{name}</span>
          <div className="flex">
            <button
              onClick={(e) => {
                e.stopPropagation();
                minimizeWindow(name);
              }}
              className="p-1 hover:bg-gray-300"
            >
              <AiOutlineMinus />
            </button>
            <button
              onClick={(e) => {
                e.stopPropagation();
                maximizeWindow(name);
              }}
              className="p-1 hover:bg-gray-300"
            >
              <AiOutlineFullscreen />
            </button>
            <button
              onClick={(e) => {
                e.stopPropagation();
                closeWindow(name);
              }}
              className="p-1 hover:bg-gray-300"
            >
              <AiOutlineClose />
            </button>
          </div>
        </div>
        {/* Window Content */}
        <div className="flex-grow overflow-auto window-content">{children}</div>
      </motion.div>
    </Rnd>
  );
};

export default DraggableWindows;

AND:

// components/AnimatedBackground.tsx

'use client';

import React, { useEffect, useRef } from 'react';
import { useWindowManager } from '../contexts/WindowManagerContext';

interface AnimatedBackgroundProps {
  gridSpacing?: number;
  effectRadius?: number;
  maxDisplacement?: number;
  dotColor?: string;
  dotSize?: number;
}

const AnimatedBackground: React.FC<AnimatedBackgroundProps> = ({
  gridSpacing = 25,
  effectRadius = 500,
  maxDisplacement = 10,
  dotColor = '#888',
  dotSize = 1.5,
}) => {
  // Import window manager context to access window positions and properties
  const { windows, windowStates } = useWindowManager();

  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const animationFrameId = useRef<number | null>(null);
  const dots = useRef<{ x: number; y: number }[]>([]); // Store positions of dots
  const mousePosition = useRef<{ x: number; y: number }>({ x: -1000, y: -1000 }); // Initialize off-screen

  useEffect(() => {
    const canvas = canvasRef.current;
    const context = canvas?.getContext('2d');

    if (!canvas || !context) return;

    let width = window.innerWidth;
    let height = window.innerHeight;
    canvas.width = width;
    canvas.height = height;

    // Function to initialize dots
    const initializeDots = () => {
      const columns = Math.floor(width / gridSpacing) + 1;
      const rows = Math.floor(height / gridSpacing) + 1;

      // Initialize dots
      const dotArray: { x: number; y: number }[] = [];
      for (let row = 0; row < rows; row++) {
        for (let col = 0; col < columns; col++) {
          dotArray.push({ x: col * gridSpacing, y: row * gridSpacing });
        }
      }
      dots.current = dotArray;
    };

    initializeDots();

    // Handle window resize
    const handleResize = () => {
      width = window.innerWidth;
      height = window.innerHeight;
      canvas.width = width;
      canvas.height = height;

      // Reinitialize dots on resize
      initializeDots();
    };

    window.addEventListener('resize', handleResize);

    // Handle mouse move
    const handleMouseMove = (event: MouseEvent) => {
      mousePosition.current = { x: event.clientX, y: event.clientY };
    };

    window.addEventListener('mousemove', handleMouseMove);

    // Animation loop
    const animate = () => {
      if (!context) return;

      context.clearRect(0, 0, canvas.width, canvas.height);
      context.fillStyle = dotColor; // Color of the dots

      // Prepare gravity sources
      const gravitySources: {
        x: number;
        y: number;
        gravity: number;
        effectRadius: number;
        maxDisplacement: number;
      }[] = [];

      // Add mouse as a gravity source
      const useMouseGravity = true; // You can make this a prop if needed
      if (useMouseGravity) {
        gravitySources.push({
          x: mousePosition.current.x,
          y: mousePosition.current.y,
          gravity: 1.0,           // Gravity strength of the mouse
          effectRadius: effectRadius,
          maxDisplacement: maxDisplacement,
        });
      }

      // Add windows with gravity
      Object.keys(windows).forEach((name) => {
        const windowData = windows[name];
        const windowState = windowStates[name];
        const gravity = windowData.windowProps.gravity;

        if (gravity && gravity > 0 && windowState.visible && !windowState.minimized) {
          gravitySources.push({
            x: windowState.x + windowState.width / 2, // Center of the window
            y: windowState.y + windowState.height / 2,
            gravity: gravity,                   // Use gravity from windowProps
            effectRadius: effectRadius,         // You can customize per window if needed
            maxDisplacement: maxDisplacement,
          });
        }
      });

      // For each dot, calculate displacement due to all gravity sources
      for (let i = 0; i < dots.current.length; i++) {
        const dot = dots.current[i];

        let totalDisplacementX = 0;
        let totalDisplacementY = 0;

        gravitySources.forEach((source) => {
          const dx = source.x - dot.x;
          const dy = source.y - dot.y;
          const distance = Math.sqrt(dx * dx + dy * dy);

          if (distance < source.effectRadius) {
            const angle = Math.atan2(dy, dx);
            const strength = (source.effectRadius - distance) / source.effectRadius;

            // Multiply strength by gravity value
            const gravityStrength = source.gravity * strength;

            const displacementX = Math.cos(angle) * gravityStrength * source.maxDisplacement;
            const displacementY = Math.sin(angle) * gravityStrength * source.maxDisplacement;

            totalDisplacementX += displacementX;
            totalDisplacementY += displacementY;
          }
        });

        // Draw the dot with the total displacement
        context.beginPath();
        context.arc(
          dot.x + totalDisplacementX,
          dot.y + totalDisplacementY,
          dotSize, // Radius of each dot
          0,
          Math.PI * 2
        );
        context.fill();
      }

      animationFrameId.current = requestAnimationFrame(animate);
    };

    animate(); // Start the animation

    // Cleanup on unmount
    return () => {
      window.removeEventListener('resize', handleResize);
      window.removeEventListener('mousemove', handleMouseMove);
      if (animationFrameId.current !== null) {
        cancelAnimationFrame(animationFrameId.current);
      }
    };
  }, [
    gridSpacing,
    effectRadius,
    maxDisplacement,
    dotColor,
    dotSize,
    windows,
    windowStates,
  ]);

  return (
    <canvas
      ref={canvasRef}
      className="absolute inset-0 pointer-events-none"
      style={{ display: 'block' }}
    />
  );
};

export default AnimatedBackground;

TASK: The background must update the gravity stuff when dragging the windows because currently, it only updates once window has finished moving, but I want it to update WHILE window is moving.