'use client';

import { useRef, useEffect, useState, useCallback } from 'react';
import { useCanvasStore } from '../../store/canvasStore';
import { CANVAS_CONFIG } from '@gaplace/shared';

interface VirtualCanvasProps {
  onPixelClick: (x: number, y: number) => void;
  onCursorMove: (x: number, y: number) => void;
  onChunkNeeded: (chunkX: number, chunkY: number) => void;
  onHoverChange: (x: number, y: number, pixelInfo: { color: string; userId?: string } | null) => void;
  selectedColor: string;
}

export function VirtualCanvas({ onPixelClick, onCursorMove, onChunkNeeded, onHoverChange, selectedColor }: VirtualCanvasProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const animationFrameRef = useRef<number | undefined>(undefined);
  const isMouseDownRef = useRef(false);
  const isPanningRef = useRef(false);
  const lastPanPointRef = useRef<{ x: number; y: number } | null>(null);
  const mouseDownPositionRef = useRef<{ x: number; y: number } | null>(null);
  const DRAG_THRESHOLD = 5; // pixels
  const [cursorStyle, setCursorStyle] = useState<'crosshair' | 'grab' | 'grabbing'>('crosshair');
  const [hoverPixel, setHoverPixel] = useState<{ x: number; y: number } | null>(null);
  
  // Touch handling state
  const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null);
  const lastTouchesRef = useRef<TouchList | null>(null);
  const pinchStartDistanceRef = useRef<number | null>(null);
  const pinchStartZoomRef = useRef<number | null>(null);

  const {
    viewport,
    chunks,
    selectedTool,
    showGrid,
    userCursors,
    setViewport,
    setZoom,
    pan,
    getChunkCoordinates,
    getPixelAt,
    getPixelInfo,
  } = useCanvasStore();

  // Large modern pixel size - fixed base size to avoid circular dependencies
  const BASE_PIXEL_SIZE = 32;
  const pixelSize = BASE_PIXEL_SIZE * viewport.zoom;

  // Convert screen coordinates to canvas coordinates
  const screenToCanvas = useCallback((screenX: number, screenY: number) => {
    const rect = canvasRef.current?.getBoundingClientRect();
    if (!rect) return { x: 0, y: 0 };

    const x = Math.floor((screenX - rect.left + viewport.x) / pixelSize);
    const y = Math.floor((screenY - rect.top + viewport.y) / pixelSize);
    return { x, y };
  }, [viewport.x, viewport.y, pixelSize]);

  // Convert canvas coordinates to screen coordinates
  const canvasToScreen = useCallback((canvasX: number, canvasY: number) => {
    return {
      x: canvasX * pixelSize - viewport.x,
      y: canvasY * pixelSize - viewport.y,
    };
  }, [viewport.x, viewport.y, pixelSize]);

  // Track requested chunks to prevent spam
  const requestedChunksRef = useRef(new Set<string>());

  // Get visible chunks and request loading if needed
  const getVisibleChunks = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return [];

    const startX = Math.floor(viewport.x / pixelSize);
    const startY = Math.floor(viewport.y / pixelSize);
    const endX = Math.floor((viewport.x + canvas.width) / pixelSize);
    const endY = Math.floor((viewport.y + canvas.height) / pixelSize);

    const startChunkX = Math.floor(startX / CANVAS_CONFIG.DEFAULT_CHUNK_SIZE);
    const startChunkY = Math.floor(startY / CANVAS_CONFIG.DEFAULT_CHUNK_SIZE);
    const endChunkX = Math.floor(endX / CANVAS_CONFIG.DEFAULT_CHUNK_SIZE);
    const endChunkY = Math.floor(endY / CANVAS_CONFIG.DEFAULT_CHUNK_SIZE);

    const visibleChunks: Array<{ chunkX: number; chunkY: number }> = [];

    for (let chunkX = startChunkX; chunkX <= endChunkX; chunkX++) {
      for (let chunkY = startChunkY; chunkY <= endChunkY; chunkY++) {
        visibleChunks.push({ chunkX, chunkY });
        
        // Request chunk if not loaded and not already requested
        const chunkKey = `${chunkX},${chunkY}`;
        const chunk = chunks.get(chunkKey);
        if (!chunk || !chunk.isLoaded) {
          if (!requestedChunksRef.current.has(chunkKey)) {
            requestedChunksRef.current.add(chunkKey);
            onChunkNeeded(chunkX, chunkY);
            // Remove from requested after a delay to allow retry
            setTimeout(() => {
              requestedChunksRef.current.delete(chunkKey);
            }, 5000);
          }
        }
      }
    }

    return visibleChunks;
  }, [viewport, pixelSize, chunks, onChunkNeeded]);

  // Track dirty state to avoid unnecessary renders
  const isDirtyRef = useRef(true);
  const lastRenderTimeRef = useRef(0);
  const MIN_RENDER_INTERVAL = 16; // ~60fps max

  // Render function with performance optimizations
  const render = useCallback(() => {
    const now = performance.now();
    if (now - lastRenderTimeRef.current < MIN_RENDER_INTERVAL) {
      return;
    }
    
    if (!isDirtyRef.current) {
      return;
    }
    
    const canvas = canvasRef.current;
    const ctx = canvas?.getContext('2d');
    if (!canvas || !ctx) return;

    // Clear canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Set pixel rendering
    ctx.imageSmoothingEnabled = false;

    const visibleChunks = getVisibleChunks();

    // Render pixels from visible chunks
    for (const { chunkX, chunkY } of visibleChunks) {
      const chunkKey = `${chunkX},${chunkY}`;
      const chunk = chunks.get(chunkKey);
      
      if (!chunk || !chunk.isLoaded) continue;

      for (const [pixelKey, pixelInfo] of chunk.pixels) {
        const [localX, localY] = pixelKey.split(',').map(Number);
        const worldX = chunkX * CANVAS_CONFIG.DEFAULT_CHUNK_SIZE + localX;
        const worldY = chunkY * CANVAS_CONFIG.DEFAULT_CHUNK_SIZE + localY;
        
        const screenPos = canvasToScreen(worldX, worldY);
        
        // Only render if pixel is visible
        if (
          screenPos.x >= -pixelSize &&
          screenPos.y >= -pixelSize &&
          screenPos.x < canvas.width &&
          screenPos.y < canvas.height
        ) {
          ctx.fillStyle = pixelInfo.color;
          ctx.fillRect(screenPos.x, screenPos.y, pixelSize, pixelSize);
        }
      }
    }

    // Render grid only when enabled and zoomed in enough (pixel size > 16px)
    if (showGrid && pixelSize > 16) {
      ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
      ctx.lineWidth = 1;
      ctx.shadowColor = 'rgba(0, 0, 0, 0.1)';
      ctx.shadowBlur = 2;
      
      const startX = Math.floor(viewport.x / pixelSize) * pixelSize - viewport.x;
      const startY = Math.floor(viewport.y / pixelSize) * pixelSize - viewport.y;
      
      // Vertical grid lines
      for (let x = startX; x < canvas.width; x += pixelSize) {
        ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, canvas.height);
        ctx.stroke();
      }
      
      // Horizontal grid lines
      for (let y = startY; y < canvas.height; y += pixelSize) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(canvas.width, y);
        ctx.stroke();
      }
      
      // Reset shadow
      ctx.shadowBlur = 0;
    }

    // Render user cursors
    for (const [userId, cursor] of userCursors) {
      const screenPos = canvasToScreen(cursor.x, cursor.y);
      
      if (
        screenPos.x >= 0 &&
        screenPos.y >= 0 &&
        screenPos.x < canvas.width &&
        screenPos.y < canvas.height
      ) {
        // Draw cursor
        ctx.strokeStyle = cursor.color;
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(screenPos.x + pixelSize / 2, screenPos.y + pixelSize / 2, pixelSize + 4, 0, Math.PI * 2);
        ctx.stroke();
        
        // Draw username
        ctx.fillStyle = cursor.color;
        ctx.font = '12px sans-serif';
        ctx.fillText(cursor.username, screenPos.x, screenPos.y - 8);
      }
    }

    // Render hover cursor indicator when zoomed in enough
    if (hoverPixel && pixelSize > 32) {
      const screenPos = canvasToScreen(hoverPixel.x, hoverPixel.y);
      
      if (
        screenPos.x >= 0 &&
        screenPos.y >= 0 &&
        screenPos.x < canvas.width &&
        screenPos.y < canvas.height
      ) {
        // Draw subtle pixel highlight border
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
        ctx.lineWidth = 2;
        ctx.setLineDash([4, 4]);
        ctx.strokeRect(screenPos.x + 1, screenPos.y + 1, pixelSize - 2, pixelSize - 2);
        ctx.setLineDash([]); // Reset line dash
        
        // Draw small corner indicators
        ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
        const cornerSize = 3;
        // Top-left corner
        ctx.fillRect(screenPos.x, screenPos.y, cornerSize, cornerSize);
        // Top-right corner
        ctx.fillRect(screenPos.x + pixelSize - cornerSize, screenPos.y, cornerSize, cornerSize);
        // Bottom-left corner
        ctx.fillRect(screenPos.x, screenPos.y + pixelSize - cornerSize, cornerSize, cornerSize);
        // Bottom-right corner
        ctx.fillRect(screenPos.x + pixelSize - cornerSize, screenPos.y + pixelSize - cornerSize, cornerSize, cornerSize);
      }
    }
    
    isDirtyRef.current = false;
    lastRenderTimeRef.current = now;
  }, [viewport, chunks, pixelSize, showGrid, userCursors, hoverPixel]);

  // Mouse event handlers - Left click for both pixel placement and panning
  const handleMouseDown = (e: React.MouseEvent) => {
    e.preventDefault();
    
    if (e.button === 0) { // Left click only
      isMouseDownRef.current = true;
      isPanningRef.current = false; // Reset panning state
      mouseDownPositionRef.current = { x: e.clientX, y: e.clientY };
      lastPanPointRef.current = { x: e.clientX, y: e.clientY };
      setCursorStyle('grab');
    }
  };

  const handleMouseMove = (e: React.MouseEvent) => {
    const { x, y } = screenToCanvas(e.clientX, e.clientY);
    onCursorMove(x, y);
    
    // Update hover pixel for cursor indicator
    setHoverPixel({ x, y });
    
    // Get pixel info at this position and call onHoverChange
    const pixelInfo = getPixelInfo(x, y);
    onHoverChange(x, y, pixelInfo);

    if (isMouseDownRef.current && mouseDownPositionRef.current && lastPanPointRef.current) {
      // Calculate distance from initial mouse down position
      const deltaFromStart = Math.sqrt(
        Math.pow(e.clientX - mouseDownPositionRef.current.x, 2) +
        Math.pow(e.clientY - mouseDownPositionRef.current.y, 2)
      );

      // If moved more than threshold, start panning
      if (deltaFromStart > DRAG_THRESHOLD && !isPanningRef.current) {
        isPanningRef.current = true;
        setCursorStyle('grabbing');
      }

      // If we're panning, update viewport
      if (isPanningRef.current) {
        const deltaX = lastPanPointRef.current.x - e.clientX;
        const deltaY = lastPanPointRef.current.y - e.clientY;
        pan(deltaX, deltaY);
        lastPanPointRef.current = { x: e.clientX, y: e.clientY };
      }
    }
  };

  const handleMouseUp = (e: React.MouseEvent) => {
    if (e.button === 0) {
      // If we weren't panning, treat as pixel click
      if (!isPanningRef.current && mouseDownPositionRef.current) {
        const { x, y } = screenToCanvas(e.clientX, e.clientY);
        onPixelClick(x, y);
      }

      // Reset all mouse state
      isMouseDownRef.current = false;
      isPanningRef.current = false;
      lastPanPointRef.current = null;
      mouseDownPositionRef.current = null;
      setCursorStyle('crosshair');
    }
  };

  const handleMouseLeave = () => {
    // Stop all interactions when mouse leaves canvas
    isMouseDownRef.current = false;
    isPanningRef.current = false;
    lastPanPointRef.current = null;
    mouseDownPositionRef.current = null;
    setHoverPixel(null);
    setCursorStyle('crosshair');
  };

  const handleWheel = (e: React.WheelEvent) => {
    e.preventDefault();
    
    // Get mouse position in screen coordinates (relative to canvas)
    const rect = canvasRef.current?.getBoundingClientRect();
    if (!rect) return;
    
    const mouseScreenX = e.clientX - rect.left;
    const mouseScreenY = e.clientY - rect.top;
    
    // Calculate world position that mouse is pointing to
    const worldX = (mouseScreenX + viewport.x) / pixelSize;
    const worldY = (mouseScreenY + viewport.y) / pixelSize;
    
    // Calculate new zoom with better zoom increments
    const zoomFactor = e.deltaY > 0 ? 0.8 : 1.25;
    const newZoom = Math.max(0.1, Math.min(10.0, viewport.zoom * zoomFactor));
    
    // Calculate new pixel size
    const newPixelSize = BASE_PIXEL_SIZE * newZoom;
    
    // Calculate new viewport position to keep world position under cursor
    const newViewportX = worldX * newPixelSize - mouseScreenX;
    const newViewportY = worldY * newPixelSize - mouseScreenY;
    
    // Update viewport with new zoom and position
    setViewport({
      zoom: newZoom,
      x: newViewportX,
      y: newViewportY,
    });
  };

  // Resize handler
  useEffect(() => {
    const handleResize = () => {
      const container = containerRef.current;
      const canvas = canvasRef.current;
      if (!container || !canvas) return;

      const rect = container.getBoundingClientRect();
      const devicePixelRatio = window.devicePixelRatio || 1;
      
      // Set the internal size to actual resolution
      canvas.width = rect.width * devicePixelRatio;
      canvas.height = rect.height * devicePixelRatio;
      
      // Scale the canvas back down using CSS
      canvas.style.width = rect.width + 'px';
      canvas.style.height = rect.height + 'px';
      
      // Scale the drawing context so everything draws at the correct size
      const ctx = canvas.getContext('2d');
      if (ctx) {
        ctx.scale(devicePixelRatio, devicePixelRatio);
      }
      
      setViewport({ width: rect.width, height: rect.height });
    };

    handleResize();
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, [setViewport]);

  // Mark as dirty when viewport, chunks, or other dependencies change
  useEffect(() => {
    isDirtyRef.current = true;
  }, [viewport, chunks, userCursors, hoverPixel]);

  // Animation loop with render on demand
  useEffect(() => {
    const animate = () => {
      render();
      animationFrameRef.current = requestAnimationFrame(animate);
    };

    animationFrameRef.current = requestAnimationFrame(animate);

    return () => {
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
      }
    };
  }, [render]);

  // Touch event handlers for mobile
  const handleTouchStart = (e: React.TouchEvent) => {
    e.preventDefault();
    
    if (e.touches.length === 1) {
      // Single touch - potential tap or pan
      const touch = e.touches[0];
      touchStartRef.current = {
        x: touch.clientX,
        y: touch.clientY,
        time: Date.now()
      };
      lastPanPointRef.current = { x: touch.clientX, y: touch.clientY };
    } else if (e.touches.length === 2) {
      // Two finger pinch to zoom
      const touch1 = e.touches[0];
      const touch2 = e.touches[1];
      const distance = Math.sqrt(
        Math.pow(touch1.clientX - touch2.clientX, 2) + 
        Math.pow(touch1.clientY - touch2.clientY, 2)
      );
      pinchStartDistanceRef.current = distance;
      pinchStartZoomRef.current = viewport.zoom;
      
      // Center point between touches for zoom
      const centerX = (touch1.clientX + touch2.clientX) / 2;
      const centerY = (touch1.clientY + touch2.clientY) / 2;
      lastPanPointRef.current = { x: centerX, y: centerY };
    }
    
    lastTouchesRef.current = Array.from(e.touches) as any;
  };

  const handleTouchMove = (e: React.TouchEvent) => {
    e.preventDefault();
    
    if (e.touches.length === 1 && touchStartRef.current && lastPanPointRef.current) {
      // Single touch pan
      const touch = e.touches[0];
      const deltaX = lastPanPointRef.current.x - touch.clientX;
      const deltaY = lastPanPointRef.current.y - touch.clientY;
      
      // Check if we've moved enough to start panning
      const totalDistance = Math.sqrt(
        Math.pow(touch.clientX - touchStartRef.current.x, 2) + 
        Math.pow(touch.clientY - touchStartRef.current.y, 2)
      );
      
      if (totalDistance > DRAG_THRESHOLD) {
        isPanningRef.current = true;
        
        // Pan the viewport
        const newViewportX = viewport.x + deltaX;
        const newViewportY = viewport.y + deltaY;
        
        setViewport({
          x: newViewportX,
          y: newViewportY,
        });
        
        lastPanPointRef.current = { x: touch.clientX, y: touch.clientY };
      }
      
      // Update hover for single touch
      const { x, y } = screenToCanvas(touch.clientX, touch.clientY);
      setHoverPixel({ x, y });
      const pixelInfo = getPixelInfo(x, y);
      onHoverChange(x, y, pixelInfo);
      
    } else if (e.touches.length === 2 && pinchStartDistanceRef.current && pinchStartZoomRef.current) {
      // Two finger pinch zoom
      const touch1 = e.touches[0];
      const touch2 = e.touches[1];
      const currentDistance = Math.sqrt(
        Math.pow(touch1.clientX - touch2.clientX, 2) + 
        Math.pow(touch1.clientY - touch2.clientY, 2)
      );
      
      const scale = currentDistance / pinchStartDistanceRef.current;
      const newZoom = Math.max(0.1, Math.min(5.0, pinchStartZoomRef.current * scale));
      
      // Zoom towards center of pinch
      const rect = canvasRef.current?.getBoundingClientRect();
      if (rect) {
        const centerX = (touch1.clientX + touch2.clientX) / 2 - rect.left;
        const centerY = (touch1.clientY + touch2.clientY) / 2 - rect.top;
        
        const worldX = (centerX + viewport.x) / pixelSize;
        const worldY = (centerY + viewport.y) / pixelSize;
        
        const newPixelSize = BASE_PIXEL_SIZE * newZoom;
        const newViewportX = worldX * newPixelSize - centerX;
        const newViewportY = worldY * newPixelSize - centerY;
        
        setViewport({
          zoom: newZoom,
          x: newViewportX,
          y: newViewportY,
        });
      }
    }
  };

  const handleTouchEnd = (e: React.TouchEvent) => {
    e.preventDefault();
    
    if (e.touches.length === 0) {
      // All touches ended
      if (touchStartRef.current && !isPanningRef.current) {
        // This was a tap, not a pan
        const timeDiff = Date.now() - touchStartRef.current.time;
        if (timeDiff < 300) { // Quick tap
          const { x, y } = screenToCanvas(touchStartRef.current.x, touchStartRef.current.y);
          onPixelClick(x, y);
        }
      }
      
      // Reset touch state
      touchStartRef.current = null;
      isPanningRef.current = false;
      lastPanPointRef.current = null;
      pinchStartDistanceRef.current = null;
      pinchStartZoomRef.current = null;
    }
    
    lastTouchesRef.current = Array.from(e.touches) as any;
  };

  return (
    <div 
      ref={containerRef}
      className="fixed inset-0 w-full h-full"
      style={{ 
        cursor: cursorStyle,
        background: `
          linear-gradient(135deg, 
            rgba(15, 23, 42, 0.95) 0%,
            rgba(30, 41, 59, 0.9) 25%,
            rgba(51, 65, 85, 0.85) 50%,
            rgba(30, 58, 138, 0.8) 75%,
            rgba(29, 78, 216, 0.75) 100%
          ),
          radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
          radial-gradient(circle at 80% 80%, rgba(59, 130, 246, 0.12) 0%, transparent 40%),
          radial-gradient(circle at 40% 70%, rgba(147, 51, 234, 0.1) 0%, transparent 40%),
          radial-gradient(circle at 70% 30%, rgba(16, 185, 129, 0.08) 0%, transparent 40%)
        `,
        backgroundAttachment: 'fixed'
      }}
    >
      <canvas
        ref={canvasRef}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
        onMouseLeave={handleMouseLeave}
        onWheel={handleWheel}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
        onContextMenu={(e) => e.preventDefault()}
        className="w-full h-full touch-none"
        style={{ touchAction: 'none' }}
      />
    </div>
  );
}