import React, { useRef, useState, useEffect, useCallback } from 'react';
import { DrawingCanvasProps, Point, Shape, ShapeType } from 'l33t-lib';
import { Command, createShapeCommand } from './commands';
import { EventHandlerDependencies, createCanvasEventHandlers } from './canvasEventHandlers';
const generateId = () => {
  return Math.random().toString(36).substr(2, 9);
};

const StreamCanvas = ({ backgroundImage, initialShapes , onSave }:DrawingCanvasProps) => {

  const [shapes, setShapes] = useState<Record<string, Shape>>(initialShapes);

  // const [shapes, setShapes] = useState<{ [id: string]: Shape }>(() => {
  //   const shapesObject: { [id: string]: Shape } = {};

  //   if(initialShapes)
  //     initialShapes.forEach(shape => {
  //       shapesObject[shape.id] = shape;
  //     });
  //   return shapesObject;
  // });


  const [currentShape, setCurrentShape] = useState<Shape | null>(null);
  const [selectedShape, setSelectedShape] = useState<Shape | null>(null);
  const [selectedPoint, setSelectedPoint] = useState<{ shapeId: string, pointIndex: number } | null>(null);
  const [drawMode, setDrawMode] = useState<ShapeType>('polygon');
  const [isDragging, setIsDragging] = useState(false);
  const [isDraggingShape, setIsDraggingShape] = useState(false);
  const [draggedShapeId, setDraggedShapeId] = useState<string | null>(null);
  const [hoveredPoint, setHoveredPoint] = useState<{ shapeId: string, pointIndex: number } | null>(null);
  const backgroundCanvasRef = useRef<HTMLCanvasElement>(null);
  const drawingCanvasRef = useRef<HTMLCanvasElement>(null);
  const dragStartPosition = useRef<Point | null>(null);
  const draggedShape = useRef<Shape | null>(null);
  const draggedPointInfo = useRef<{ shapeId: string, pointIndex: number } | null>(null);

  const [editingId, setEditingId] = useState<string | null>(null);
  const [editValue, setEditValue] = useState<string>('');
  const editTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const [hoveredShapeId, setHoveredShapeId] = useState<string | null>(null);

  const [isEditing, setIsEditing] = useState(false);
  const handleRowClick = useCallback((shape: Shape) => {
    if (!isEditing) {
      setSelectedShape(shape);
    }
  }, [isEditing]);

  const commandHistory = useRef<Command[]>([]);
  const redoStack = useRef<Command[]>([]);
  const undo = useCallback(() => {
    const command = commandHistory.current.pop();
    if (command) {
      command.undo();
      redoStack.current.push(command);
    }
  }, []);

  const redo = useCallback(() => {
    const command = redoStack.current.pop();
    if (command) {
      command.execute();
      commandHistory.current.push(command);
    }
  }, []);

  useEffect(() => {
    drawBackground();
    drawShapes();
    resizeCanvases()
  }, [shapes, currentShape, selectedShape, selectedPoint, hoveredPoint]);

  const resizeCanvases = () => {
    const container = backgroundCanvasRef.current?.parentElement;
    if (!container) return;

    const { width, height } = container.getBoundingClientRect();
    
    [backgroundCanvasRef, drawingCanvasRef].forEach(canvasRef => {
      if (canvasRef.current) {
        canvasRef.current.width = width;
        canvasRef.current.height = height;
        canvasRef.current.style.width = `${width}px`;
        canvasRef.current.style.height = `${height}px`;
      }
    });
    drawBackground();
    drawShapes();
  };


  useEffect(() => {

    resizeCanvases();
    window.addEventListener('resize', resizeCanvases);

    return () => window.removeEventListener('resize', resizeCanvases);
  }, []);

  const handleKeyDown = useCallback((event: KeyboardEvent) => {
    const { key, metaKey, ctrlKey, shiftKey } = event;
  
    if (key === 'Backspace' && selectedPoint) {
      // deleteSelectedVertex();
    }
    else if (key === 'Backspace' && selectedShape) {
      // deleteSelectedShape();
    }
    else if (metaKey || ctrlKey) {
      if (key === 'z') {
        event.preventDefault();
        shiftKey ? redo() : undo();
      } else if (key === 'y') {
        event.preventDefault();
        redo();
      }
    }
  }, [
    selectedPoint, 
    selectedShape, 
    undo, 
    redo
  ]);





  useEffect(() => {
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [handleKeyDown]);



  /*
  * All functions are defined here
  */




  const saveShapes = useCallback(() => {
    // Create a copy of the current shapes record
    const shapesToSave = { ...shapes };
    
    // Add current shape if it exists and is closed
    if (currentShape && currentShape.isClosed) {
      shapesToSave[currentShape.id] = currentShape;
    }

    // Filter out unclosed shapes
    Object.keys(shapesToSave).forEach(key => {
      if (!shapesToSave[key].isClosed) {
        delete shapesToSave[key];
      }
    });

    // Save the record of shapes
    onSave(shapesToSave);
  }, [shapes, currentShape, onSave]);

  const drawBackground = useCallback(() => {
    const canvas = backgroundCanvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

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

    const img = new Image();
    img.onload = () => {
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    };
    img.src = backgroundImage;
  }, [backgroundImage]);

  const isPointNearOtherPoint = useCallback((point1: Point, point2: Point, threshold: number = 10): boolean => {
    const dx = point1.x - point2.x;
    const dy = point1.y - point2.y;
    return Math.sqrt(dx * dx + dy * dy) < threshold;
  }, []);

  const isPointOnLine = useCallback((point: Point, lineStart: Point, lineEnd: Point): boolean => {
    const distanceThreshold = 5;
    const d1 = Math.sqrt(Math.pow(point.x - lineStart.x, 2) + Math.pow(point.y - lineStart.y, 2));
    const d2 = Math.sqrt(Math.pow(point.x - lineEnd.x, 2) + Math.pow(point.y - lineEnd.y, 2));
    const lineLength = Math.sqrt(Math.pow(lineEnd.x - lineStart.x, 2) + Math.pow(lineEnd.y - lineStart.y, 2));
    return Math.abs(d1 + d2 - lineLength) < distanceThreshold;
  }, []);

  const getCoordinates = useCallback((event: React.MouseEvent): Point => {
    const canvas = drawingCanvasRef.current;
    if (!canvas) return { x: 0, y: 0 };

    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;

    const x = (event.clientX - rect.left) * scaleX;
    const y = (event.clientY - rect.top) * scaleY;
    return { x, y };
  }, []);

  const drawShapes = useCallback(() => {
    const canvas = drawingCanvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    [...Object.values(shapes), currentShape].filter(Boolean).forEach((shape) => {
      if (!shape) return;

      // Draw shape outline
      ctx.beginPath();
      // Shape is red only when selected AND no vertex is selected
      ctx.strokeStyle = (shape === selectedShape && !selectedPoint) ? 'red' : 'black';
      ctx.lineWidth = shape.id === hoveredShapeId || shape === selectedShape ? 3 : 2;

      shape.points?.forEach((point, index) => {
        if (index === 0) {
          ctx.moveTo(point.x, point.y);
        } else {
          ctx.lineTo(point.x, point.y);
        }
      });

      // Only close the path visually if the shape is marked as closed
      if (shape.type === 'polygon' && shape.isClosed) {
        ctx.lineTo(shape.points[0].x, shape.points[0].y); // Connect back to first point
        ctx.closePath();
        ctx.fillStyle = 'rgba(0, 0, 255, 0.1)';
        ctx.fill();
      }
      ctx.stroke();

      // Draw vertices
      shape.points?.forEach((point, index) => {
        ctx.beginPath();

        const isSelected = selectedPoint && 
                          selectedPoint.shapeId === shape.id && 
                          selectedPoint.pointIndex === index;
        const isHovered = hoveredPoint && 
                         hoveredPoint.shapeId === shape.id &&
                         hoveredPoint.pointIndex === index;
        
        let pointRadius = 8;  
        if (isHovered) pointRadius = 9;
        if (isSelected) pointRadius = 10;  

        ctx.arc(point.x, point.y, pointRadius, 0, 2 * Math.PI);

        if (isSelected) {
          ctx.fillStyle = 'red';
        } else if (isHovered) {
          ctx.fillStyle = 'blue';
        } else {
          ctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
        }

        ctx.fill();
        ctx.strokeStyle = 'white';
        ctx.lineWidth = isSelected ? 3 : 2;
        ctx.stroke();
      });
    });
  }, [shapes, currentShape, selectedShape, selectedPoint, hoveredPoint, hoveredShapeId]);

  // 
  /*** end functions  */


  /**
   * commands whith undo/redo 
   *  */
  const commandContext = useCallback(() => ({
    shapes,
    setShapes,
    setCurrentShape,
    setSelectedShape,
    setSelectedPoint,
    selectedShape,
    selectedPoint,
  }), [shapes, selectedShape, selectedPoint]);

  const executeCommand = useCallback((command: Command) => {
    command.execute();
    commandHistory.current.push(command);
    redoStack.current = [];
  }, []);


  /*** commands whith undo/redo  */

  const addPoint = useCallback((point: Point) => {
    const command = createShapeCommand('AddPoint', commandContext(), {
      point,
      drawMode,
      currentShape: currentShape || {
        id: generateId(),
        type: drawMode,
        points: [],
        isClosed: false
      }
    });
    executeCommand(command);
  }, [currentShape, drawMode, commandContext, executeCommand]);

  const closePolygon = useCallback(() => {
    if (currentShape && currentShape.points.length > 2) {
      const command = createShapeCommand('ClosePolygon', commandContext(), {
        currentShape: {
          ...currentShape,
          isClosed: true,
          points: [...currentShape.points]
        }
      });
      executeCommand(command);
    }
  }, [currentShape, commandContext, executeCommand]);

  const deleteSelectedShape = useCallback(() => {
    if (selectedShape) {
      const command = createShapeCommand('DeleteShape', commandContext(), {
        currentShape: selectedShape
      });
      executeCommand(command);
      setCurrentShape(null);
      drawShapes();
    }
  }, [selectedShape, commandContext, executeCommand, drawShapes]);

  const deleteSelectedVertex = useCallback(() => {
    if (selectedPoint) {
      const command = createShapeCommand('DeleteVertex', commandContext(), {
        currentShape: shapes[selectedPoint.shapeId],
        selectedPoint: selectedPoint
      });
      executeCommand(command);
    }
  }, [selectedPoint, shapes, commandContext, executeCommand]);

  const insertPointIntoShape = useCallback((shapeId: string, index: number, point: Point) => {
    const command = createShapeCommand('InsertPointInShape', commandContext(), {
      currentShape: shapes[shapeId],
      shapeId: shapeId,
      index: index,
      point: point
    });
    executeCommand(command);
  }, [shapes, commandContext, executeCommand]);

  const isPointInsidePolygon = useCallback((point: Point, polygon: Point[]): boolean => {
    let inside = false;
    for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
      const xi = polygon[i].x, yi = polygon[i].y;
      const xj = polygon[j].x, yj = polygon[j].y;

      const intersect = ((yi > point.y) !== (yj > point.y))
        && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
      if (intersect) inside = !inside;
    }
    return inside;
  }, []);

  const _getShape = useCallback((point: Point): Shape | null => {
    for (const shape of Object.values(shapes)) {
      if (shape.type === 'polygon' && shape.isClosed === true) {
        const isInside = isPointInsidePolygon(point, shape.points);
        if (isInside) return shape;
      }
    }
    return null;
  }, [shapes, isPointInsidePolygon]);

  const handleCanvasClick = useCallback((event: React.MouseEvent) => {
    const point = getCoordinates(event);
    const clickedPolygon = _getShape(point);

    if (clickedPolygon) {
      setSelectedShape(clickedPolygon);
      setSelectedPoint(null);
      return;
    }

    if (currentShape && currentShape.type === 'polygon' && currentShape.points.length > 2) {
      const startPoint = currentShape.points[0];
      if (isPointNearOtherPoint(point, startPoint)) {
        // Close the polygon without adding the first point again
        closePolygon();
        return;
      }
    }



    const clickedShape = Object.values(shapes).find(shape =>
      shape.points?.some((shapePoint, index) => {
        if (isPointNearOtherPoint(point, shapePoint)) {
          setSelectedPoint({ shapeId: shape.id, pointIndex: index });
          setSelectedShape(shape);
          return true;
        }
        return false;
      })
    );

    if (clickedShape) {
      return;
    }

    const clickedLine = Object.values(shapes).find(shape =>
      shape.points?.some((shapePoint, index) => {
        if (index < shape.points.length - 1) {
          const nextPoint = shape.points[index + 1];
          if (isPointOnLine(point, shapePoint, nextPoint)) {
            insertPointIntoShape(shape.id, index + 1, point);
            return true;
          }
        }
        return false;
      })
    );

    if (clickedLine) {
      return;
    }

    addPoint(point);
  }, [shapes, getCoordinates, currentShape, isPointNearOtherPoint, isPointOnLine, insertPointIntoShape, addPoint, closePolygon, _getShape]);

  const updateShapeId = useCallback((oldId: string, newId: string) => {
    if (oldId === newId || shapes[newId]) return;

    setShapes(prevShapes => {
      const updatedShapes = { ...prevShapes };
      const shape = updatedShapes[oldId];
      delete updatedShapes[oldId];
      updatedShapes[newId] = { ...shape, id: newId };
      return updatedShapes;
    });

    if (selectedShape?.id === oldId) {
      setSelectedShape(prevSelected => prevSelected ? { ...prevSelected, id: newId } : null);
    }
    setEditingId(null);
  }, [shapes, selectedShape]);

  const handleIdChange = useCallback((shapeId: string, newId: string) => {
    if (editTimeoutRef.current) {
      clearTimeout(editTimeoutRef.current);
    }

    editTimeoutRef.current = setTimeout(() => {
      updateShapeId(shapeId, newId);
    }, 500);
  }, [updateShapeId]);


  const startEditing = useCallback((shapeId: string) => {
    setIsEditing(true);
    setEditingId(shapeId);
    setEditValue(shapeId);
  }, []);

  const stopEditing = useCallback(() => {
    setIsEditing(false);
    if (editingId && editValue) {
      handleIdChange(editingId, editValue);
    }
    setEditingId(null);
    setEditValue('');
  }, [editingId, editValue, handleIdChange]);



  /*** end undo / redo  commands  */
  const eventHandlerDeps: EventHandlerDependencies = {
    shapes,
    currentShape,
    selectedShape,
    selectedPoint,
    isDragging,
    isDraggingShape,
    draggedShapeId,
    setShapes,
    setCurrentShape,
    setSelectedShape,
    setSelectedPoint,
    setIsDragging,
    setIsDraggingShape,
    setDraggedShapeId,
    setHoveredPoint,
    drawingCanvasRef,
    dragStartPosition,
    draggedShape,
    draggedPointInfo,
    commandContext,
    executeCommand,
    getCoordinates,
    isPointNearOtherPoint,
    isPointOnLine,
    _getShape,
  };

  const {
    handleCanvasMouseMove,
    handleCanvasMouseDown,
    handleCanvasMouseUp
  } = createCanvasEventHandlers(eventHandlerDeps);


  return (
    <div className="flex-col w-full bg-black bg-opacity-30 rounded-lg overflow-hidden">
      <div className="relative w-full h-full aspect-video">
        <canvas
          ref={backgroundCanvasRef}
          className="absolute top-0 left-0 w-full h-full"
        />
        <canvas
          ref={drawingCanvasRef}
          className="absolute top-0 left-0 w-full h-full"
          onClick={handleCanvasClick}
          onMouseMove={handleCanvasMouseMove}
          onMouseDown={handleCanvasMouseDown}
          onMouseUp={handleCanvasMouseUp}
        />
      </div>
      <div className="flex justify-between w-full p-2">
        <div>
          <button
            className={`px-4 py-2 mr-2 rounded ${drawMode === 'polygon' ? 'bg-indigo-700 text-white' : 'bg-gray-200'}`}
            onClick={() => setDrawMode('polygon')}
          >
            Polygon
          </button>
          <button
            className={`px-4 py-2 rounded ${drawMode === 'line' ? 'bg-indigo-700 text-white' : 'bg-gray-200'}`}
            onClick={() => setDrawMode('line')}
          >
            Line
          </button>
        </div>
        <button
          className="ms-auto me-2 px-4 py-2 bg-red-500 text-white rounded"
          onClick={(e) => {
            e.stopPropagation(); // Prevent row click
           
            if (selectedPoint)
              deleteSelectedVertex();
            else if (selectedShape) {
              deleteSelectedShape();
            }

          }}

          disabled={!selectedShape && !selectedPoint}
        >
          Delete Selected
        </button>
        <button
          className="px-4 py-2 bg-green-500 text-white rounded"
          onClick={saveShapes}
        >
          Apply
        </button>

      </div>

      <table className="min-w-full divide-y divide-black mt-4">
        <thead className="bg-indgo-700">
          <tr>
            <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-400  uppercase tracking-wider">
              ID
            </th>
            <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
              Type
            </th>
            <th></th>
          </tr>
        </thead>

        <tbody className="divide-y divide-black">
          {Object.values(shapes).map((shape) => (
            <tr
              key={shape.id}
              className={`
                transition-colors duration-200 ease-in-out
                ${hoveredShapeId === shape.id ? 'bg-indigo-700' : ''}
                ${selectedShape?.id === shape.id ? 'bg-indigo-700' : 'hover:bg-indigo-600'}
              `}
              onClick={() => handleRowClick(shape)}
              // onMouseEnter={() => handleRowMouseEnter(shape)}
              // onMouseLeave={handleRowMouseLeave}
            >

              <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
                {editingId === shape.id ? (
                  <input
                    value={editValue}
                    onChange={(e) => setEditValue(e.target.value)}
                    onBlur={stopEditing}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') {
                        stopEditing();
                      }
                    }}
                    className="bg-white text-black px-3 py-1 rounded-md border border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all duration-200 ease-in-out shadow-sm hover:shadow-md w-full"
                    autoFocus
                    onClick={(e) => e.stopPropagation()}
                    
                  />
                ) : (
                  <span
                    onClick={(e) => {
                      e.stopPropagation();
                      startEditing(shape.id);
                    }}
                    className="cursor-pointer hover:text-indigo-300 transition-colors duration-200 ease-in-out"
                  >
                    {shape.id}
                  </span>
                )}
              </td>

              <td className="px-6 py-4 whitespace-nowrap text-sm">
                {shape.type}
              </td>

              <td>
                <button
                  onClick={(e) => {
                    e.stopPropagation(); // Prevent row click
                    setSelectedShape(shape);
                    const command = createShapeCommand('DeleteShape', commandContext(), {
                      currentShape: shape // Use the shape directly instead of relying on selectedShape state
                    });
                    executeCommand(command);
                    setCurrentShape(null);
                  }}
                  className="text-red-500 hover:text-red-700 transition-colors duration-200 ease-in-out"
                >
                  <i className="fas fa-trash-alt"></i>
                </button>
              </td>
          
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

export default StreamCanvas;