import React, { useRef, useEffect, useMemo, useCallback } 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";

interface ArmBotState {
  uuid: string;
  position: [number, number, number];
  rotation: [number, number, number];
  status: string;
  material: string;
  triggerId: string;
  connections: {
    source: { modelUUID: string; pointUUID: string };
    targets: { modelUUID: string; pointUUID: string }[];
  };
  actions: {
    uuid: string;
    name: string;
    speed: number;
    processes: { triggerId: string; startPoint: string; endPoint: string }[];
  };
  isActive?: boolean;
}
interface ProcessContainerProps {
  processes: ProcessData[];
  setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
  agvRef: any;
  MaterialRef: any;
  armBots: ArmBotState[];
  setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
}

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

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

  const baseMaterials = useMemo(
    () => ({
      Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
      Crate: 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

  // Add this function to ProcessAnimator component
  const isConnectedToActiveArmBot = useCallback(
    (processId: any) => {
      // Check if any active armbot is connected to this process
      return armBots.some((armbot) => {
        if (!armbot.isActive) return false;

        // Check if this armbot is connected to the process
        return armbot.connections?.targets?.some((connection) => {
          // Find the process that owns this modelUUID
          const connectedProcess = processes.find((p) =>
            p.paths?.some((path) => path.modeluuid === connection.modelUUID)
          );
          return connectedProcess?.id === processId;
        });
      });
    },
    [armBots, processes]
  );

  // First useFrame for spawn logic
  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;

        // Check connection status
        const isConnected = isConnectedToActiveArmBot(process.id);

        if (processState.isProcessDelaying) {
          // Existing delay handling logic...
          return;
        }

        if (isConnected) {
          newStates[process.id] = {
            ...processState,
            nextSpawnTime: Infinity, // Prevent future spawns
          };
          return;
        }

        const spawnPoint = findSpawnPoint(process);
        if (!spawnPoint || !spawnPoint.actions) {
          // console.log(
          //   `Process ${process.id} has no valid spawn point or 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 || "0") || 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
          );

          // Initialize state properly to ensure animation
          newObject.state = {
            ...newObject.state,
            isAnimating: true,
            isDelaying: false,
            delayComplete: false,
            progress: 0.005, // Start with tiny progress to ensure animation begins
          };

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

  // Second useFrame for animation logic
  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;
        }

        // Check connection status with debugging
        const isConnected = isConnectedToActiveArmBot(process.id);
        // console.log(
        //   `Process ${process.id} animation - connected:`,
        //   isConnected
        // );

        if (isConnected) {
          // Stop all animations when connected to active arm bot
          newStates[process.id] = {
            ...processState,
            spawnedObjects: Object.entries(processState.spawnedObjects).reduce(
              (acc, [id, obj]) => ({
                ...acc,
                [id]: {
                  ...obj,
                  state: {
                    ...obj.state,
                    isAnimating: false, // Stop animation
                    isDelaying: false, // Clear delays
                    delayComplete: false, // Reset delays
                    progress: 0, // Reset progress
                  },
                },
              }),
              {}
            ),
          };
          return;
        }

        // Process delay handling
        if (processState.isProcessDelaying) {
          const effectiveDelayTime =
            processState.processDelayDuration / speedRef.current;

          if (
            currentTime - processState.processDelayStartTime >=
            effectiveDelayTime
          ) {
            // console.log(
            //   `Process ${process.id} delay completed, resuming animation`
            // );
            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;
          } else {
            return;
          }
        }

        // Ensure we have a valid path to follow
        const path =
          process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) ||
          [];

        if (path.length < 2) {
          // console.log(
          //   `Process ${process.id} has insufficient path points: ${path.length}`
          // );
          return;
        }

        const updatedObjects = { ...processState.spawnedObjects };
        let animationOccurring = false; // Track if any animation is happening

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

            const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
            if (!currentRef) {
              // console.log(
              //   `No reference for object ${objectId}, skipping animation`
              // );
              return;
            }

            // Initialize position for new objects
            if (
              obj.position &&
              obj.state.currentIndex === 0 &&
              obj.state.progress === 0
            ) {
              currentRef.position.copy(obj.position);
            }

            const stateRef = obj.state;

            // Ensure animation state is properly set for objects
            if (!stateRef.isAnimating && !stateRef.isDelaying && !isConnected) {
              stateRef.isAnimating = true;
              stateRef.progress =
                stateRef.progress > 0 ? stateRef.progress : 0.005;
            }

            // Handle delay logic
            if (stateRef.isDelaying) {
              const effectiveDelayTime =
                stateRef.currentDelayDuration / speedRef.current;

              if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) {
                // console.log(
                //   `Delay complete for object ${objectId}, resuming animation`
                // );
                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;
              }
            }

            // Skip non-animating objects
            if (!stateRef.isAnimating) {
              // console.log(
              //   `Object ${objectId} not animating, skipping animation updates`
              // );
              return;
            }

            animationOccurring = true; // Mark that animation is happening

            // Handle point actions
            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;

            // Handle objects at the last point
            if (isLastPoint) {
              const isAgvPicking = agvRef.current.some(
                (agv: any) =>
                  agv.processId === process.id && agv.status === "picking"
              );

              const shouldHide =
                !currentPointData?.actions ||
                !hasNonInheritActions(currentPointData.actions);

              if (shouldHide) {
                if (isAgvPicking) {
                  // console.log(
                  //   `AGV picking at last point for object ${objectId}, hiding object`
                  // );
                  updatedObjects[objectId] = {
                    ...obj,
                    visible: false,
                    state: {
                      ...stateRef,
                      isAnimating: false,
                    },
                  };
                } else {
                  tempStackedObjectsRef.current[objectId] = true;

                  updatedObjects[objectId] = {
                    ...obj,
                    visible: true,
                    state: {
                      ...stateRef,
                      isAnimating: true,
                    },
                  };
                }

                return;
              }
            }

            // Handle stacked objects when AGV picks
            if (tempStackedObjectsRef.current[objectId]) {
              const isAgvPicking = agvRef.current.some(
                (agv: any) =>
                  agv.processId === process.id && agv.status === "picking"
              );

              if (isAgvPicking) {
                delete tempStackedObjectsRef.current[objectId];

                updatedObjects[objectId] = {
                  ...obj,
                  visible: false,
                  state: {
                    ...stateRef,
                    isAnimating: false,
                  },
                };

                return;
              }
            }

            // Handle normal animation progress for objects not at last point
            if (!isLastPoint) {
              const nextPoint = path[nextPointIdx];
              const distance =
                path[stateRef.currentIndex].distanceTo(nextPoint);
              const effectiveSpeed = stateRef.speed * speedRef.current;
              const movement = effectiveSpeed * delta;

              // Ensure progress is always moving forward
              if (stateRef.delayComplete && stateRef.progress < 0.01) {
                stateRef.progress = 0.05;
                stateRef.delayComplete = false;
                // console.log(
                //   `Boosting progress for object ${objectId} after delay`
                // );
              } else {
                stateRef.progress += movement / distance;
                // console.log(
                //   `Object ${objectId} progress: ${stateRef.progress.toFixed(3)}`
                // );
              }

              // Handle point transition
              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
                );

                // No action needed with newPointData here - will be handled in next frame
              } else {
                // Update position with lerp
                currentRef.position.lerpVectors(
                  path[stateRef.currentIndex],
                  nextPoint,
                  stateRef.progress
                );
              }
            }

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

        // Log if no animation is occurring when it should
        if (!animationOccurring && !isConnected) {
          // console.log(
          //   `Warning: No animation occurring for process ${process.id} despite not being connected`
          // );
        }

        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;
