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

interface AnimationState {
  currentIndex: number;
  progress: number;
  isAnimating: boolean;
  speed: number;
  isDelaying: boolean;
  delayStartTime: number;
  currentDelayDuration: number;
  delayComplete: boolean;
  currentPathIndex: number;
  spawnPoints: Record<
    string,
    {
      position: THREE.Vector3;
      interval: number;
      lastSpawnTime: number;
    }
  >;
}

const MAX_SPAWNED_OBJECTS = 20;

const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
  processes,
}) => {
  const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
  const { isPlaying, setIsPlaying } = usePlayButtonStore();

  const groupRef = useRef<THREE.Group>(null);
  const meshRef = useRef<THREE.Mesh>(null);
  const [visible, setVisible] = useState(false);
  const spawnedObjectsRef = useRef<THREE.Object3D[]>([]);

  const materials = 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 }),
    }),
    []
  );

  const [currentMaterial, setCurrentMaterial] = useState<THREE.Material>(
    materials.Default
  );

  const { animationPath, currentProcess } = useMemo(() => {
    const defaultProcess = {
      animationPath: [],
      pointActions: [],
      speed: 1,
      paths: [],
    };
    const cp = processes?.[0] || defaultProcess;
    return {
      animationPath:
        cp.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || [],
      currentProcess: cp,
    };
  }, [processes]);

  const animationStateRef = useRef<AnimationState>({
    currentIndex: 0,
    progress: 0,
    isAnimating: false,
    speed: currentProcess.speed,
    isDelaying: false,
    delayStartTime: 0,
    currentDelayDuration: 0,
    delayComplete: false,
    currentPathIndex: 0,
    spawnPoints: {},
  });

  const getPointDataForAnimationIndex = (index: number) => {
    if (!processes[0]?.paths) return null;

    if (index < 3) {
      return processes[0].paths[0]?.points[index];
    } else {
      const path2Index = index - 3;
      return processes[0].paths[1]?.points[path2Index];
    }
  };

  useEffect(() => {
    if (isPlaying) {
      setVisible(true);
      animationStateRef.current = {
        currentIndex: 0,
        progress: 0,
        isAnimating: true,
        speed: currentProcess.speed,
        isDelaying: false,
        delayStartTime: 0,
        currentDelayDuration: 0,
        delayComplete: false,
        currentPathIndex: 0,
        spawnPoints: {},
      };

      // Clear spawned objects
      if (groupRef.current) {
        spawnedObjectsRef.current.forEach((obj) => {
          if (groupRef.current?.children.includes(obj)) {
            groupRef.current.remove(obj);
          }
          if (obj instanceof THREE.Mesh) {
            obj.material.dispose();
          }
        });
        spawnedObjectsRef.current = [];
      }

      const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
      if (currentRef && animationPath.length > 0) {
        currentRef.position.copy(animationPath[0]);
      }
    } else {
      animationStateRef.current.isAnimating = false;
    }
  }, [isPlaying, currentProcess, animationPath]);

  const handleMaterialSwap = (materialType: string) => {
    const newMaterial =
      materials[materialType as keyof typeof materials] || materials.Default;
    setCurrentMaterial(newMaterial);

    spawnedObjectsRef.current.forEach((obj) => {
      if (obj instanceof THREE.Mesh) {
        obj.material = newMaterial.clone();
      }
    });
  };

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

  const handlePointActions = (
    actions: PointAction[] = [],
    currentTime: number,
    currentPosition: THREE.Vector3
  ) => {
    let shouldStopAnimation = false;

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

      switch (action.type) {
        case "Delay":
          if (
            !animationStateRef.current.isDelaying &&
            !animationStateRef.current.delayComplete
          ) {
            const delayDuration =
              typeof action.delay === "number"
                ? action.delay
                : parseFloat(action.delay as string) || 0;

            if (delayDuration > 0) {
              animationStateRef.current.isDelaying = true;
              animationStateRef.current.delayStartTime = currentTime;
              animationStateRef.current.currentDelayDuration = delayDuration;
              shouldStopAnimation = true;
            }
          }
          break;

        case "Despawn":
          setVisible(false);
          setIsPlaying(false);
          animationStateRef.current.isAnimating = false;
          shouldStopAnimation = true;
          break;

        case "Spawn":
          const spawnInterval =
            typeof action.spawnInterval === "number"
              ? action.spawnInterval
              : parseFloat(action.spawnInterval as string) || 1;

          const positionKey = currentPosition.toArray().join(",");

          animationStateRef.current.spawnPoints[positionKey] = {
            position: currentPosition.clone(),
            interval: spawnInterval,
            lastSpawnTime: currentTime - spawnInterval, // Force immediate spawn
          };
          break;

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

        case "Inherit":
          break;
      }
    });

    return shouldStopAnimation;
  };

  useFrame((state, delta) => {
    const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
    if (
      !currentRef ||
      !animationStateRef.current.isAnimating ||
      animationPath.length < 2
    ) {
      return;
    }

    const currentTime = state.clock.getElapsedTime();
    const path = animationPath;
    const stateRef = animationStateRef.current;

    if (stateRef.currentIndex === 3 && stateRef.currentPathIndex === 0) {
      stateRef.currentPathIndex = 1;
    }

    const currentPointData = getPointDataForAnimationIndex(
      stateRef.currentIndex
    );

    if (stateRef.progress === 0 && currentPointData?.actions) {
      const shouldStop = handlePointActions(
        currentPointData.actions,
        currentTime,
        currentRef.position
      );
      if (shouldStop) return;
    }

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

    // Handle spawning - this is the key updated part
    Object.entries(stateRef.spawnPoints).forEach(([key, spawnPoint]) => {
      if (currentTime - spawnPoint.lastSpawnTime >= spawnPoint.interval) {
        spawnPoint.lastSpawnTime = currentTime;

        if (gltf?.scene && groupRef?.current) {
          const newObject = gltf.scene.clone();
          newObject.position.copy(spawnPoint.position);

          newObject.traverse((child) => {
            if (child instanceof THREE.Mesh) {
              child.material = currentMaterial.clone();
            }
          });

          groupRef.current.add(newObject);
          spawnedObjectsRef.current.push(newObject);

          // Clean up old objects if needed
          // console.log(
          //   "spawnedObjectsRef.current.length: ",
          //   spawnedObjectsRef.current.length
          // );
          if (spawnedObjectsRef.current.length > MAX_SPAWNED_OBJECTS) {
            const oldest = spawnedObjectsRef.current.shift();
            if (oldest && groupRef.current.children.includes(oldest)) {
              groupRef.current.remove(oldest);
              if (oldest instanceof THREE.Mesh) {
                oldest.material.dispose();
              }
            }
          }
        }
      }
    });

    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]);
          setIsPlaying(false);
          stateRef.isAnimating = false;
          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
        );
      }
    }
  });

  useEffect(() => {
    return () => {
      if (groupRef.current) {
        spawnedObjectsRef.current.forEach((obj) => {
          if (groupRef.current?.children.includes(obj)) {
            groupRef.current.remove(obj);
          }
          if (obj instanceof THREE.Mesh) {
            obj.material.dispose();
          }
        });
        spawnedObjectsRef.current = [];
      }
    };
  }, []);

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

  if (!gltf?.scene) {
    return visible ? (
      <mesh ref={meshRef} material={currentMaterial}>
        <boxGeometry args={[1, 1, 1]} />
      </mesh>
    ) : null;
  }

  return visible ? (
    <group ref={groupRef}>
      <primitive
        object={gltf.scene.clone()}
        scale={[1, 1, 1]}
        material={currentMaterial}
      />
    </group>
  ) : null;
};

export default ProcessAnimator;
