import React, { useRef, useState, useEffect, useMemo } from "react";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { GLTFLoader } from "three-stdlib";
import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import boxGltb from "../../../assets/gltf-glb/crate_box.glb";

interface PointAction {
  uuid: string;
  name: string;
  type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
  objectType: string;
  material: string;
  delay: string | number;
  spawnInterval: string | number;
  isUsed: boolean;
}

interface ProcessPoint {
  uuid: string;
  position: number[];
  rotation: number[];
  actions: PointAction[];
  connections: {
    source: { pathUUID: string; pointUUID: string };
    targets: { pathUUID: string; pointUUID: string }[];
  };
}

interface ProcessPath {
  modeluuid: string;
  modelName: string;
  points: ProcessPoint[];
  pathPosition: number[];
  pathRotation: number[];
  speed: number;
}

interface ProcessData {
  id: string;
  paths: ProcessPath[];
  animationPath: { x: number; y: number; z: number }[];
  pointActions: PointAction[][];
  speed: number;
  customMaterials?: Record<string, THREE.Material>;
  renderAs?: "box" | "custom";
}

interface AnimationState {
  currentIndex: number;
  progress: number;
  isAnimating: boolean;
  speed: number;
  isDelaying: boolean;
  delayStartTime: number;
  currentDelayDuration: number;
  delayComplete: boolean;
  currentPathIndex: number;
}

interface SpawnedObject {
  ref: React.RefObject<THREE.Group | THREE.Mesh>;
  state: AnimationState;
  visible: boolean;
  material: THREE.Material;
  spawnTime: number;
  currentMaterialType: string;
}

interface ProcessAnimationState {
  spawnedObjects: { [objectId: string]: SpawnedObject };
  nextSpawnTime: number;
  objectIdCounter: number;
}

const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
  processes,
}) => {
  console.log("processes: ", processes);
  const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
  const { isPlaying } = usePlayButtonStore();
  const groupRef = useRef<THREE.Group>(null);

  const [animationStates, setAnimationStates] = useState<
    Record<string, ProcessAnimationState>
  >({});

  // Base materials
  const baseMaterials = useMemo(
    () => ({
      Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
      Box: new THREE.MeshStandardMaterial({
        color: 0xcccccc,
        metalness: 0.8,
        roughness: 0.2,
      }),
      Crate: new THREE.MeshStandardMaterial({
        color: 0x00aaff,
        metalness: 0.1,
        roughness: 0.5,
      }),
      Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
    }),
    []
  );

  // Initialize animation states when processes or play state changes
  useEffect(() => {
    if (!isPlaying) {
      setAnimationStates({});
      return;
    }

    const newStates: Record<string, ProcessAnimationState> = {};
    processes.forEach((process) => {
      newStates[process.id] = {
        spawnedObjects: {},
        nextSpawnTime: 0,
        objectIdCounter: 0,
      };
    });
    setAnimationStates(newStates);
  }, [isPlaying, processes]);

  // Find spawn point in a process
  const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
    for (const path of process.paths || []) {
      for (const point of path.points || []) {
        const spawnAction = point.actions?.find(
          (a) => a.isUsed && a.type === "Spawn"
        );
        if (spawnAction) {
          return point;
        }
      }
    }
    return null;
  };

  // Create a new spawned object
  const createSpawnedObject = (
    process: ProcessData,
    currentTime: number,
    materialType: string
  ): SpawnedObject => {
    const processMaterials = {
      ...baseMaterials,
      ...(process.customMaterials || {}),
    };

    return {
      ref: React.createRef(),
      state: {
        currentIndex: 0,
        progress: 0,
        isAnimating: true,
        speed: process.speed || 1,
        isDelaying: false,
        delayStartTime: 0,
        currentDelayDuration: 0,
        delayComplete: false,
        currentPathIndex: 0,
      },
      visible: true,
      material:
        processMaterials[materialType as keyof typeof processMaterials] ||
        baseMaterials.Default,
      currentMaterialType: materialType,
      spawnTime: currentTime,
    };
  };

  // Handle material swap for an object
  const handleMaterialSwap = (
    processId: string,
    objectId: string,
    materialType: string
  ) => {
    setAnimationStates((prev) => {
      const processState = prev[processId];
      if (!processState || !processState.spawnedObjects[objectId]) return prev;

      const process = processes.find((p) => p.id === processId);
      const processMaterials = {
        ...baseMaterials,
        ...(process?.customMaterials || {}),
      };

      const newMaterial =
        processMaterials[materialType as keyof typeof processMaterials] ||
        baseMaterials.Default;

      return {
        ...prev,
        [processId]: {
          ...processState,
          spawnedObjects: {
            ...processState.spawnedObjects,
            [objectId]: {
              ...processState.spawnedObjects[objectId],
              material: newMaterial,
              currentMaterialType: materialType,
            },
          },
        },
      };
    });
  };

  // Handle point actions for an object
  const handlePointActions = (
    processId: string,
    objectId: string,
    actions: PointAction[] = [],
    currentTime: number
  ): boolean => {
    let shouldStopAnimation = false;

    actions.forEach((action) => {
      if (!action.isUsed) return;

      switch (action.type) {
        case "Delay":
          setAnimationStates((prev) => {
            const processState = prev[processId];
            if (
              !processState ||
              !processState.spawnedObjects[objectId] ||
              processState.spawnedObjects[objectId].state.isDelaying
            ) {
              return prev;
            }

            const delayDuration =
              typeof action.delay === "number"
                ? action.delay
                : parseFloat(action.delay as string) || 0;

            if (delayDuration > 0) {
              return {
                ...prev,
                [processId]: {
                  ...processState,
                  spawnedObjects: {
                    ...processState.spawnedObjects,
                    [objectId]: {
                      ...processState.spawnedObjects[objectId],
                      state: {
                        ...processState.spawnedObjects[objectId].state,
                        isDelaying: true,
                        delayStartTime: currentTime,
                        currentDelayDuration: delayDuration,
                        delayComplete: false,
                      },
                    },
                  },
                },
              };
            }
            return prev;
          });
          shouldStopAnimation = true;
          break;

        case "Despawn":
          setAnimationStates((prev) => {
            const processState = prev[processId];
            if (!processState) return prev;

            const newSpawnedObjects = { ...processState.spawnedObjects };
            delete newSpawnedObjects[objectId];

            return {
              ...prev,
              [processId]: {
                ...processState,
                spawnedObjects: newSpawnedObjects,
              },
            };
          });
          shouldStopAnimation = true;
          break;

        case "Swap":
          if (action.material) {
            handleMaterialSwap(processId, objectId, action.material);
          }
          break;

        default:
          break;
      }
    });

    return shouldStopAnimation;
  };

  // Check if point has non-inherit actions
  const hasNonInheritActions = (actions: PointAction[] = []): boolean => {
    return actions.some((action) => action.isUsed && action.type !== "Inherit");
  };

  // Get point data for current animation index
  const getPointDataForAnimationIndex = (
    process: ProcessData,
    index: number
  ): ProcessPoint | null => {
    if (!process.paths) return null;

    let cumulativePoints = 0;
    for (const path of process.paths) {
      const pointCount = path.points?.length || 0;

      if (index < cumulativePoints + pointCount) {
        const pointIndex = index - cumulativePoints;
        return path.points?.[pointIndex] || null;
      }

      cumulativePoints += pointCount;
    }

    return null;
  };

  // Spawn objects for all processes
  useFrame((state) => {
    if (!isPlaying) return;

    const currentTime = state.clock.getElapsedTime();
    setAnimationStates((prev) => {
      const newStates = { ...prev };

      processes.forEach((process) => {
        const processState = newStates[process.id];
        if (!processState) return;

        const spawnPoint = findSpawnPoint(process);
        if (!spawnPoint || !spawnPoint.actions) return;

        const spawnAction = spawnPoint.actions.find(
          (a) => a.isUsed && a.type === "Spawn"
        );
        if (!spawnAction) return;

        const spawnInterval =
          typeof spawnAction.spawnInterval === "number"
            ? spawnAction.spawnInterval
            : 0;

        if (currentTime >= processState.nextSpawnTime) {
          const objectId = `obj-${process.id}-${processState.objectIdCounter}`;

          newStates[process.id] = {
            ...processState,
            spawnedObjects: {
              ...processState.spawnedObjects,
              [objectId]: createSpawnedObject(
                process,
                currentTime,
                spawnAction.material || "Default"
              ),
            },
            objectIdCounter: processState.objectIdCounter + 1,
            nextSpawnTime: currentTime + spawnInterval,
          };
        }
      });

      return newStates;
    });
  });

  // Animate objects for all processes
  useFrame((state, delta) => {
    if (!isPlaying) return;

    const currentTime = state.clock.getElapsedTime();
    setAnimationStates((prev) => {
      const newStates = { ...prev };

      processes.forEach((process) => {
        const processState = newStates[process.id];
        if (!processState) return;

        const path =
          process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) ||
          [];
        if (path.length < 2) return;

        const updatedObjects = { ...processState.spawnedObjects };

        Object.entries(processState.spawnedObjects).forEach(
          ([objectId, obj]) => {
            if (!obj.visible || !obj.state.isAnimating) return;

            const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
            if (!currentRef) return;

            const stateRef = obj.state;

            // Get current point data
            const currentPointData = getPointDataForAnimationIndex(
              process,
              stateRef.currentIndex
            );

            // Execute actions when arriving at a new point
            if (stateRef.progress === 0 && currentPointData?.actions) {
              const shouldStop = handlePointActions(
                process.id,
                objectId,
                currentPointData.actions,
                currentTime
              );
              if (shouldStop) return;
            }

            // Handle delays
            if (stateRef.isDelaying) {
              if (
                currentTime - stateRef.delayStartTime >=
                stateRef.currentDelayDuration
              ) {
                stateRef.isDelaying = false;
                stateRef.delayComplete = true;
              } else {
                return; // Keep waiting
              }
            }

            const nextPointIdx = stateRef.currentIndex + 1;
            const isLastPoint = nextPointIdx >= path.length;

            if (isLastPoint) {
              if (currentPointData?.actions) {
                const shouldStop = !hasNonInheritActions(
                  currentPointData.actions
                );
                if (shouldStop) {
                  currentRef.position.copy(path[stateRef.currentIndex]);
                  delete updatedObjects[objectId];
                  return;
                }
              }
            }

            if (!isLastPoint) {
              const nextPoint = path[nextPointIdx];
              const distance =
                path[stateRef.currentIndex].distanceTo(nextPoint);
              const movement = stateRef.speed * delta;
              stateRef.progress += movement / distance;

              if (stateRef.progress >= 1) {
                stateRef.currentIndex = nextPointIdx;
                stateRef.progress = 0;
                stateRef.delayComplete = false;
                currentRef.position.copy(nextPoint);
              } else {
                currentRef.position.lerpVectors(
                  path[stateRef.currentIndex],
                  nextPoint,
                  stateRef.progress
                );
              }
            }

            updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
          }
        );

        newStates[process.id] = {
          ...processState,
          spawnedObjects: updatedObjects,
        };
      });

      return newStates;
    });
  });

  if (!processes || processes.length === 0) {
    return null;
  }

  return (
    <>
      {Object.entries(animationStates).flatMap(([processId, processState]) =>
        Object.entries(processState.spawnedObjects)
          .filter(([_, obj]) => obj.visible)
          .map(([objectId, obj]) => {
            const process = processes.find((p) => p.id === processId);
            console.log("process: ", process);
            const renderAs = process?.renderAs || "custom";

            return renderAs === "box" ? (
              <mesh
                key={objectId}
                ref={obj.ref as React.RefObject<THREE.Mesh>}
                material={obj.material}
              >
                <boxGeometry args={[1, 1, 1]} />
              </mesh>
            ) : (
              gltf?.scene && (
                <group
                  key={objectId}
                  ref={obj.ref as React.RefObject<THREE.Group>}
                >
                  <primitive
                    object={gltf.scene.clone()}
                    material={obj.material}
                  />
                </group>
              )
            );
          })
      )}
    </>
  );
};

export default ProcessAnimator;
