'use client';

import { Canvas } from '@react-three/fiber';
import {
  CameraControls,
  Line,
  Text,
} from '@react-three/drei';
import { Suspense, useEffect, useRef, useState } from 'react';
import { Stack, Text as MantineText } from '@mantine/core';
import * as THREE from 'three';

// Define the shape of nodes and links from API
interface NodeData {
  id: string;
  title: string;
  coords_3d: [number, number, number];
}

interface LinkData {
  in: string; // from node id
  out: string; // to node id
}

// 1. The 3D Node Component
function Node({ node, onNodeClick }: { node: NodeData; onNodeClick: (node: NodeData) => void }) {
  const [hovered, setHovered] = useState(false);
  const [clicked, setClicked] = useState(false);

  return (
    <mesh
      position={node.coords_3d}
      onClick={(e) => {
        e.stopPropagation();
        onNodeClick(node);
        setClicked(!clicked);
      }}
      onPointerOver={(e) => {
        e.stopPropagation();
        setHovered(true);
      }}
      onPointerOut={() => setHovered(false)}
    >
      <sphereGeometry args={[0.1, 32, 32]} />
      <meshStandardMaterial
        color={hovered ? '#90c0ff' : '#e9ecef'}
        emissive={hovered ? '#90c0ff' : '#e9ecef'}
        emissiveIntensity={hovered ? 0.5 : 0.1}
      />
      {/* Show title on hover or click */}
      {(hovered || clicked) && (
        <Text
          position={[0, 0.2, 0]}
          fontSize={0.1}
          color="white"
          anchorX="center"
          anchorY="middle"
        >
          {node.title}
        </Text>
      )}
    </mesh>
  );
}

// 2. The Main Scene Component
export function ThoughtGalaxy() {
  const [nodes, setNodes] = useState<NodeData[]>([]);
  const [links, setLinks] = useState<LinkData[]>([]);
  const cameraControlsRef = useRef<CameraControls>(null);
  const hasFitCamera = useRef(false);

  // Fetch data from API on mount and poll for updates
  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch('/api/galaxy', {
          credentials: 'include', // Include cookies for authentication
        });

        if (!response.ok) {
          console.error('[ThoughtGalaxy] Failed to fetch galaxy data:', response.statusText);
          return;
        }

        const data = await response.json();

        if (data.message) {
          console.log('[ThoughtGalaxy]', data.message);
          // If calculating, poll again in 2 seconds
          setTimeout(fetchData, 2000);
          return;
        }

        setNodes(data.nodes || []);
        setLinks(data.links || []);

        console.log(`[ThoughtGalaxy] Loaded ${data.nodes?.length || 0} nodes and ${data.links?.length || 0} links`);
      } catch (error) {
        console.error('[ThoughtGalaxy] Error fetching data:', error);
      }
    }

    fetchData();
  }, []);

  // Function to fit camera to all nodes
  const fitCameraToNodes = () => {
    if (!cameraControlsRef.current || nodes.length === 0) {
      console.log('[ThoughtGalaxy] Cannot fit camera:', {
        hasRef: !!cameraControlsRef.current,
        nodesLength: nodes.length,
      });
      return;
    }

    console.log('[ThoughtGalaxy] Fitting camera to', nodes.length, 'nodes...');

    // Create a THREE.Box3 from node positions
    const box = new THREE.Box3();
    nodes.forEach((node) => {
      box.expandByPoint(new THREE.Vector3(
        node.coords_3d[0],
        node.coords_3d[1],
        node.coords_3d[2]
      ));
    });

    console.log('[ThoughtGalaxy] Bounding box:', {
      min: box.min,
      max: box.max,
      size: box.getSize(new THREE.Vector3()),
    });

    // Use CameraControls' built-in fitToBox method
    try {
      cameraControlsRef.current.fitToBox(
        box,
        false, // Don't animate on initial load
        { paddingLeft: 0.5, paddingRight: 0.5, paddingTop: 0.5, paddingBottom: 0.5 }
      );
      console.log('[ThoughtGalaxy] ✓ Camera fitted to bounds');
      hasFitCamera.current = true;
    } catch (error) {
      console.error('[ThoughtGalaxy] Error fitting camera:', error);
    }
  };

  // Fit camera when nodes change and we haven't fitted yet
  useEffect(() => {
    if (!hasFitCamera.current && nodes.length > 0) {
      // Try to fit after a short delay to ensure Canvas is ready
      const timer = setTimeout(() => {
        fitCameraToNodes();
      }, 100);
      return () => clearTimeout(timer);
    }
  }, [nodes]);

  // Map links to node positions
  const linkLines = links
    .map((link) => {
      const startNode = nodes.find((n) => n.id === link.in);
      const endNode = nodes.find((n) => n.id === link.out);
      if (startNode && endNode) {
        return {
          start: startNode.coords_3d,
          end: endNode.coords_3d,
        };
      }
      return null;
    })
    .filter(Boolean) as { start: [number, number, number]; end: [number, number, number] }[];

  // Camera animation on node click
  const handleNodeClick = (node: NodeData) => {
    if (cameraControlsRef.current) {
      // Smoothly move to look at the clicked node
      cameraControlsRef.current.moveTo(
        node.coords_3d[0],
        node.coords_3d[1],
        node.coords_3d[2],
        true // Animate
      );
    }
  };

  console.log('[ThoughtGalaxy] Rendering with', nodes.length, 'nodes and', linkLines.length, 'link lines');

  // Show message if no nodes are ready yet
  if (nodes.length === 0) {
    return (
      <Stack align="center" justify="center" style={{ height: '100vh', width: '100vw' }}>
        <MantineText size="lg" c="dimmed">
          Create at least 3 nodes to visualize your thought galaxy
        </MantineText>
        <MantineText size="sm" c="dimmed">
          Nodes with content will automatically generate embeddings and 3D coordinates
        </MantineText>
      </Stack>
    );
  }

  return (
    <Canvas
      camera={{ position: [0, 5, 10], fov: 60 }}
      style={{ width: '100%', height: '100%' }}
      gl={{ preserveDrawingBuffer: true }}
      onCreated={(state) => {
        console.log('[ThoughtGalaxy] Canvas created successfully');
        // Try to fit camera now that scene is ready
        if (!hasFitCamera.current && nodes.length > 0) {
          setTimeout(() => fitCameraToNodes(), 50);
        }
      }}
    >
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} intensity={1} />
      <CameraControls ref={cameraControlsRef} />

      <Suspense fallback={null}>
        <group>
          {/* Render all nodes */}
          {nodes.map((node) => (
            <Node key={node.id} node={node} onNodeClick={handleNodeClick} />
          ))}

          {/* Render all links */}
          {linkLines.map((line, i) => (
            <Line
              key={i}
              points={[line.start, line.end]}
              color="#495057" // gray
              lineWidth={1}
            />
          ))}
        </group>
      </Suspense>
    </Canvas>
  );
}
