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

import { useProcessAnimation } from "./useProcessAnimations";
import ProcessObject from "./processObject";
import { ProcessData } from "./types";
import { useSimulationStates } from "../../../store/store";
import { retrieveGLTF } from "../../../utils/indexDB/idbUtils";

interface ProcessContainerProps {
  processes: ProcessData[];
  setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
  agvRef: any;
  MaterialRef: any;
}

const ProcessAnimator: React.FC<ProcessContainerProps> = ({
  processes,
  setProcesses,
  agvRef,
  MaterialRef,
}) => {
  const gltf = useLoader(GLTFLoader, crate) as GLTF;
  const groupRef = useRef<THREE.Group>(null);

  const {
    animationStates,
    setAnimationStates,
    clockRef,
    elapsedBeforePauseRef,
    speedRef,
    debugRef,
    findSpawnPoint,
    createSpawnedObject,
    handlePointActions,
    hasNonInheritActions,
    getPointDataForAnimationIndex,
    processes: processedProcesses,
    checkAndCountTriggers,
  } = useProcessAnimation(processes, setProcesses, agvRef);

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

  useEffect(() => {
    // Update material references for all spawned objects
    Object.entries(animationStates).forEach(([processId, processState]) => {
      Object.keys(processState.spawnedObjects).forEach((objectId) => {
        const entry = {
          processId,
          objectId,
        };

        const materialType =
          processState.spawnedObjects[objectId]?.currentMaterialType;

        if (!materialType) {
          return;
        }

        const matRefArray = MaterialRef.current;

        // Find existing material group
        const existing = matRefArray.find(
          (entryGroup: { material: string; objects: any[] }) =>
            entryGroup.material === materialType
        );

        if (existing) {
          // Check if this processId + objectId already exists
          const alreadyExists = existing.objects.some(
            (o: any) =>
              o.processId === entry.processId && o.objectId === entry.objectId
          );

          if (!alreadyExists) {
            existing.objects.push(entry);
          }
        } else {
          // Create new group for this material type
          matRefArray.push({
            material: materialType,
            objects: [entry],
          });
        }
      });
    });
  }, [animationStates, MaterialRef, agvRef]);

  // In processAnimator.tsx - only the relevant spawn logic part that needs fixes

  useFrame(() => {
    // Spawn logic frame
    const currentTime =
      clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;

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

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

        if (processState.isProcessDelaying) {
          // Existing delay handling logic...
          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;

        // Check if this is a zero interval spawn and we already spawned an object
        if (
          spawnInterval === 0 &&
          processState.hasSpawnedZeroIntervalObject === true
        ) {
          return; // Don't spawn more objects for zero interval
        }

        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,
            baseMaterials
          );

          // Update state with the new object and flag for zero interval
          newStates[process.id] = {
            ...processState,
            spawnedObjects: {
              ...processState.spawnedObjects,
              [objectId]: newObject,
            },
            objectIdCounter: processState.objectIdCounter + 1,
            nextSpawnTime: currentTime + effectiveSpawnInterval,
            // Mark that we've spawned an object for zero interval case
            hasSpawnedZeroIntervalObject:
              spawnInterval === 0
                ? true
                : processState.hasSpawnedZeroIntervalObject,
          };
        }
      });

      return newStates;
    });
  });

  useFrame((_, delta) => {
    // Animation logic frame
    const currentTime =
      clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;

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

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

        if (processState.isProcessDelaying) {
          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) {
              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
            );

            // Handle point actions when first arriving at point
            if (stateRef.progress === 0 && currentPointData?.actions) {
              const shouldStop = handlePointActions(
                process.id,
                objectId,
                currentPointData.actions,
                currentTime,
                processedProcesses,
                baseMaterials
              );
              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) {
              if (currentPointData?.actions) {
                const hasNonInherit = hasNonInheritActions(
                  currentPointData.actions
                );
                if (!hasNonInherit) {
                  // Remove the object if all actions are inherit
                  updatedObjects[objectId] = {
                    ...obj,
                    visible: false,
                    state: { ...stateRef, isAnimating: false },
                  };
                  return;
                }
              } else {
                // No actions at last point - remove the object
                updatedObjects[objectId] = {
                  ...obj,
                  visible: false,
                  state: { ...stateRef, isAnimating: false },
                };
                return;
              }
            }

            if (!isLastPoint) {
              const nextPoint = path[nextPointIdx];
              const distance =
                path[stateRef.currentIndex].distanceTo(nextPoint);
              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);

                // TRIGGER CHECK - When object arrives at new point
                checkAndCountTriggers(
                  process.id,
                  objectId,
                  stateRef.currentIndex, // The new point index
                  processedProcesses,
                  currentTime
                );

                const newPointData = getPointDataForAnimationIndex(
                  process,
                  stateRef.currentIndex
                );
              } else {
                currentRef.position.lerpVectors(
                  path[stateRef.currentIndex],
                  nextPoint,
                  stateRef.progress
                );
              }
            }

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

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

      return newStates;
    });
  });

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

  return (
    <group ref={groupRef}>
      {Object.entries(animationStates).flatMap(([processId, processState]) =>
        Object.entries(processState.spawnedObjects)
          .filter(([_, obj]) => obj.visible)
          .map(([objectId, obj]) => {
            const process = processedProcesses.find((p) => p.id === processId);

            const renderAs = process?.renderAs || "custom";

            return (
              <ProcessObject
                key={objectId}
                objectId={objectId}
                obj={obj}
                renderAs={renderAs}
                gltf={gltf}
              />
            );
          })
      )}
    </group>
  );
};

export default ProcessAnimator;
