import React, { useRef, useState, useEffect, useMemo } from "react";
import {
  useAnimationPlaySpeed,
  usePauseButtonStore,
  usePlayButtonStore,
  useResetButtonStore,
} 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 crate from "../../../assets/gltf-glb/crate_box.glb";
import box from "../../../assets/gltf-glb/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: { modelUUID: string; pointUUID: string };
    targets: { modelUUID: 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;
  position: THREE.Vector3;
}

interface ProcessAnimationState {
  spawnedObjects: { [objectId: string]: SpawnedObject };
  nextSpawnTime: number;
  objectIdCounter: number;
  isProcessDelaying: boolean;
  processDelayStartTime: number;
  processDelayDuration: number;
}

const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
  processes,
}) => {
  const gltf = useLoader(GLTFLoader, crate) as GLTF;
  const { isPlaying, setIsPlaying } = usePlayButtonStore();
  const { isPaused, setIsPaused } = usePauseButtonStore();
  const { isReset, setReset } = useResetButtonStore();
  const groupRef = useRef<THREE.Group>(null);
  const debugRef = useRef<boolean>(false);
  const clockRef = useRef<THREE.Clock>(new THREE.Clock());
  const pauseTimeRef = useRef<number>(0);
  const elapsedBeforePauseRef = useRef<number>(0);
  const animationStatesRef = useRef<Record<string, ProcessAnimationState>>({});
  const { speed, setSpeed } = useAnimationPlaySpeed();
  const prevIsPlaying = useRef<boolean | null>(null);
  const [internalResetFlag, setInternalResetFlag] = useState(false);
  const [animationStates, setAnimationStates] = useState<
    Record<string, ProcessAnimationState>
  >({});

  // Store the speed in a ref to access the latest value in animation frames
  const speedRef = useRef<number>(speed);

  // Update the ref when speed changes
  useEffect(() => {
    speedRef.current = speed;
  }, [speed]);

  useEffect(() => {
    if (prevIsPlaying.current !== null) {
      setAnimationStates({});
    }

    // Update ref to current isPlaying after effect
    prevIsPlaying.current = isPlaying;

    // setAnimationStates({});
  }, [isPlaying]);

  // Sync ref with state
  useEffect(() => {
    animationStatesRef.current = animationStates;
  }, [animationStates]);

  // Base materials
  const baseMaterials = useMemo(
    () => ({
      Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
      Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
      Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
    }),
    []
  );

  // Replace your reset effect with this:
  useEffect(() => {
    if (isReset) {
      // 1. Mark that we're doing an internal reset
      setInternalResetFlag(true);

      // 2. Pause the animation first
      setIsPlaying(false);
      setIsPaused(false);

      // 3. Reset all animation states
      setAnimationStates({});
      animationStatesRef.current = {};

      // 4. Reset timing references
      clockRef.current = new THREE.Clock();
      elapsedBeforePauseRef.current = 0;
      pauseTimeRef.current = 0;

      // 5. Clear the external reset flag
      setReset(false);

      // 6. After state updates are complete, restart
      setTimeout(() => {
        setInternalResetFlag(false);
        setIsPlaying(true);
      }, 0);
    }
  }, [isReset, setReset, setIsPlaying, setIsPaused]);

  // Handle pause state changes
  useEffect(() => {
    if (isPaused) {
      pauseTimeRef.current = clockRef.current.getElapsedTime();
    } else if (pauseTimeRef.current > 0) {
      const pausedDuration =
        clockRef.current.getElapsedTime() - pauseTimeRef.current;
      elapsedBeforePauseRef.current += pausedDuration;
    }
  }, [isPaused]);

  // Initialize animation states when processes or play state changes
  useEffect(() => {
    if (isPlaying && !internalResetFlag) {
      const newStates: Record<string, ProcessAnimationState> = {};
      processes.forEach((process) => {
        newStates[process.id] = {
          spawnedObjects: {},
          nextSpawnTime: 0,
          objectIdCounter: 0,
          isProcessDelaying: false,
          processDelayStartTime: 0,
          processDelayDuration: 0,
        };
      });
      setAnimationStates(newStates);
      animationStatesRef.current = newStates;
      clockRef.current.start();
    }
  }, [isPlaying, processes, internalResetFlag]);

  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;
  };

  const findAnimationPathPoint = (
    process: ProcessData,
    spawnPoint: ProcessPoint
  ): THREE.Vector3 => {
    if (process.animationPath && process.animationPath.length > 0) {
      let pointIndex = 0;
      for (const path of process.paths || []) {
        for (let i = 0; i < (path.points?.length || 0); i++) {
          const point = path.points?.[i];
          if (point && point.uuid === spawnPoint.uuid) {
            if (process.animationPath[pointIndex]) {
              const p = process.animationPath[pointIndex];
              return new THREE.Vector3(p.x, p.y, p.z);
            }
          }
          pointIndex++;
        }
      }
    }
    return new THREE.Vector3(
      spawnPoint.position[0],
      spawnPoint.position[1],
      spawnPoint.position[2]
    );
  };

  const createSpawnedObject = (
    process: ProcessData,
    currentTime: number,
    materialType: string,
    spawnPoint: ProcessPoint
  ): SpawnedObject => {
    const processMaterials = {
      ...baseMaterials,
      ...(process.customMaterials || {}),
    };

    const spawnPosition = findAnimationPathPoint(process, spawnPoint);
    const material =
      processMaterials[materialType as keyof typeof processMaterials] ||
      baseMaterials.Default;

    if (debugRef.current) {
      console.log(`Creating object with material: ${materialType}`, material);
    }

    return {
      ref: React.createRef(),
      state: {
        currentIndex: 0,
        progress: 0,
        isAnimating: true,
        speed: process.speed || 1, // Process base speed (will be multiplied by global speed)
        isDelaying: false,
        delayStartTime: 0,
        currentDelayDuration: 0,
        delayComplete: false,
        currentPathIndex: 0,
      },
      visible: true,
      material: material,
      currentMaterialType: materialType,
      spawnTime: currentTime,
      position: spawnPosition,
    };
  };

  const handleMaterialSwap = (
    processId: string,
    objectId: string,
    materialType: string
  ) => {
    if (debugRef.current) {
      console.log(`Attempting material swap to: ${materialType}`);
    }

    setAnimationStates((prev) => {
      const processState = prev[processId];
      if (!processState || !processState.spawnedObjects[objectId]) {
        if (debugRef.current) console.log("Object not found for swap");
        return prev;
      }

      const process = processes.find((p) => p.id === processId);
      if (!process) {
        if (debugRef.current) console.log("Process not found");
        return prev;
      }

      const processMaterials = {
        ...baseMaterials,
        ...(process.customMaterials || {}),
      };

      const newMaterial =
        processMaterials[materialType as keyof typeof processMaterials];
      if (!newMaterial) {
        if (debugRef.current) console.log(`Material ${materialType} not found`);
        return prev;
      }

      if (debugRef.current) {
        console.log(`Swapping material for ${objectId} to ${materialType}`);
      }

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

  const handlePointActions = (
    processId: string,
    objectId: string,
    actions: PointAction[] = [],
    currentTime: number
  ): boolean => {
    let shouldStopAnimation = false;

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

      if (debugRef.current) {
        console.log(`Processing action: ${action.type} for ${objectId}`);
      }

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

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

            if (delayDuration > 0) {
              return {
                ...prev,
                [processId]: {
                  ...processState,
                  isProcessDelaying: true,
                  processDelayStartTime: currentTime,
                  processDelayDuration: delayDuration,
                  spawnedObjects: {
                    ...processState.spawnedObjects,
                    [objectId]: {
                      ...processState.spawnedObjects[objectId],
                      state: {
                        ...processState.spawnedObjects[objectId].state,
                        isAnimating: false,
                        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;
  };

  const hasNonInheritActions = (actions: PointAction[] = []): boolean => {
    return actions.some((action) => action.isUsed && action.type !== "Inherit");
  };

  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;
  };

  useFrame(() => {
    if (!isPlaying || isPaused) return;

    const currentTime =
      clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;

    setAnimationStates((prev) => {
      const newStates = { ...prev };

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

        if (processState.isProcessDelaying) {
          // Apply global speed to delays (faster speed = shorter delays)
          const effectiveDelayTime =
            processState.processDelayDuration / speedRef.current;

          if (
            currentTime - processState.processDelayStartTime >=
            effectiveDelayTime
          ) {
            newStates[process.id] = {
              ...processState,
              isProcessDelaying: false,
              spawnedObjects: Object.entries(
                processState.spawnedObjects
              ).reduce(
                (acc, [id, obj]) => ({
                  ...acc,
                  [id]: {
                    ...obj,
                    state: {
                      ...obj.state,
                      isDelaying: false,
                      delayComplete: true,
                      isAnimating: true,
                      progress:
                        obj.state.progress === 0 ? 0.001 : obj.state.progress,
                    },
                  },
                }),
                {}
              ),
            };
          }
          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
            : parseFloat(spawnAction.spawnInterval as string) || 0;

        // Apply global speed to spawn intervals (faster speed = more frequent spawns)
        const effectiveSpawnInterval = spawnInterval / speedRef.current;

        if (currentTime >= processState.nextSpawnTime) {
          const objectId = `obj-${process.id}-${processState.objectIdCounter}`;
          const newObject = createSpawnedObject(
            process,
            currentTime,
            spawnAction.material || "Default",
            spawnPoint
          );

          newStates[process.id] = {
            ...processState,
            spawnedObjects: {
              ...processState.spawnedObjects,
              [objectId]: newObject,
            },
            objectIdCounter: processState.objectIdCounter + 1,
            nextSpawnTime: currentTime + effectiveSpawnInterval,
          };
        }
      });

      return newStates;
    });
  });

  useFrame((_, delta) => {
    if (!isPlaying || isPaused) return;

    const currentTime =
      clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;

    setAnimationStates((prev) => {
      const newStates = { ...prev };

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

        if (processState.isProcessDelaying) {
          // Apply global speed to delays (faster speed = shorter delays)
          const effectiveDelayTime =
            processState.processDelayDuration / speedRef.current;

          if (
            currentTime - processState.processDelayStartTime >=
            effectiveDelayTime
          ) {
            newStates[process.id] = {
              ...processState,
              isProcessDelaying: false,
              spawnedObjects: Object.entries(
                processState.spawnedObjects
              ).reduce(
                (acc, [id, obj]) => ({
                  ...acc,
                  [id]: {
                    ...obj,
                    state: {
                      ...obj.state,
                      isDelaying: false,
                      delayComplete: true,
                      isAnimating: true,
                      progress:
                        obj.state.progress === 0 ? 0.005 : obj.state.progress,
                    },
                  },
                }),
                {}
              ),
            };
            return newStates;
          } else {
            return newStates;
          }
        }

        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) return;

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

            if (
              obj.position &&
              obj.state.currentIndex === 0 &&
              obj.state.progress === 0
            ) {
              currentRef.position.copy(obj.position);
            }

            const stateRef = obj.state;

            if (stateRef.isDelaying) {
              // Apply global speed to delays (faster speed = shorter delays)
              const effectiveDelayTime =
                stateRef.currentDelayDuration / speedRef.current;

              if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) {
                stateRef.isDelaying = false;
                stateRef.delayComplete = true;
                stateRef.isAnimating = true;

                if (stateRef.progress === 0) {
                  stateRef.progress = 0.005;
                }

                const nextPointIdx = stateRef.currentIndex + 1;
                if (nextPointIdx < path.length) {
                  const slightProgress = Math.max(stateRef.progress, 0.005);
                  currentRef.position.lerpVectors(
                    path[stateRef.currentIndex],
                    nextPointIdx < path.length
                      ? path[nextPointIdx]
                      : path[stateRef.currentIndex],
                    slightProgress
                  );
                }
              } else {
                updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
                return;
              }
            }

            if (!stateRef.isAnimating) return;

            const currentPointData = getPointDataForAnimationIndex(
              process,
              stateRef.currentIndex
            );

            if (stateRef.progress === 0 && currentPointData?.actions) {
              if (debugRef.current) {
                console.log(
                  `At point ${stateRef.currentIndex} with actions:`,
                  currentPointData.actions
                );
              }
              const shouldStop = handlePointActions(
                process.id,
                objectId,
                currentPointData.actions,
                currentTime
              );
              if (shouldStop) {
                updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
                return;
              }
            }

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

            if (isLastPoint) {
              if (currentPointData?.actions) {
                const shouldStop = !hasNonInheritActions(
                  currentPointData.actions
                );
                if (shouldStop) {
                  return;
                }
              }
            }

            if (!isLastPoint) {
              const nextPoint = path[nextPointIdx];
              const distance =
                path[stateRef.currentIndex].distanceTo(nextPoint);

              // Apply both process-specific speed and global speed multiplier
              const effectiveSpeed = stateRef.speed * speedRef.current;
              const movement = effectiveSpeed * delta;

              if (stateRef.delayComplete && stateRef.progress < 0.01) {
                stateRef.progress = 0.05;
                stateRef.delayComplete = false;
              } else {
                stateRef.progress += movement / distance;
              }

              if (stateRef.progress >= 1) {
                stateRef.currentIndex = nextPointIdx;
                stateRef.progress = 0;
                currentRef.position.copy(nextPoint);

                const newPointData = getPointDataForAnimationIndex(
                  process,
                  stateRef.currentIndex
                );

                if (newPointData?.actions && debugRef.current) {
                  console.log(
                    `Reached new point with actions:`,
                    newPointData.actions
                  );
                }
              } 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);
            const renderAs = process?.renderAs || "custom";

            if (renderAs === "box") {
              return (
                <mesh
                  key={objectId}
                  ref={obj.ref as React.RefObject<THREE.Mesh>}
                  material={obj.material}
                  position={obj.position}
                >
                  <boxGeometry args={[1, 1, 1]} />
                </mesh>
              );
            }

            if (gltf?.scene) {
              // Clone the scene and apply the material to all meshes
              const clonedScene = gltf.scene.clone();
              clonedScene.traverse((child) => {
                if (child instanceof THREE.Mesh) {
                  child.material = obj.material;
                }
              });

              return (
                <group
                  key={objectId}
                  ref={obj.ref as React.RefObject<THREE.Group>}
                  position={obj.position}
                >
                  <primitive object={clonedScene} />
                </group>
              );
            }

            return null;
          })
      )}
    </>
  );
};

export default ProcessAnimator;
