'use strict';

import memoize from 'memoize-one';
import React from 'react';
import ReactNative, {
  requireNativeComponent,
  NativeModules,
  UIManager,
  PanResponder,
  PixelRatio,
  Platform,
  processColor,
  Dimensions,
} from 'react-native';
import {requestPermissions} from './handlePermissions';
import type {
  SketchCanvasProps,
  CanvasText,
  PathData,
  Path,
  Shape,
  ShapeData,
  ShapeType,
  Orientation,
} from './types';

const SketchViewName = 'RNSketchCanvas';
// Add TypeScript interface for the native component
interface NativeSketchCanvasProps {
  style?: any;
  strokeColor?: string;
  strokeWidth?: number;
  onStrokeStart?: (event: any) => void;
  onStrokeChanged?: (event: any) => void;
  onStrokeEnd?: (event: any) => void;
  onShapeStart?: (event: any) => void;
  onShapeChanged?: (event: any) => void;
  onShapeEnd?: (event: any) => void;
  onPathsChange?: (event: any) => void;
  onShapesChange?: (event: any) => void;
  onSketchSaved?: (event: any) => void;
  touchEnabled?: boolean;
  text?: any[];
  localSourceImage?: any;
  permissionDialogTitle?: string;
  permissionDialogMessage?: string;
  drawMode?: string;
  shapeFilled?: boolean;
  shapes?: any[];
}

// Register the native component
const RNSketchCanvas =
  requireNativeComponent<NativeSketchCanvasProps>(SketchViewName);
const SketchCanvasManager = NativeModules.RNSketchCanvasManager || {};

type CanvasState = {
  text: any;
  currentShape: Shape | null;
  isDrawingShape: boolean;
  selectedTextId: number | null;
  isMovingText: boolean;
  textMoveOffset: {x: number; y: number} | null;
  currentOrientation: Orientation;
  // Shape selection and manipulation state
  selectedShapeId: number | null;
  isMovingShape: boolean;
  isResizingShape: boolean;
  shapeMoveOffset: {x: number; y: number} | null;
  resizeHandle: string | null;
};

interface SketchCanvasPropsWithTextMode extends SketchCanvasProps {
  pendingTextMode?: boolean;
  onTextPlaced?: (position: {x: number; y: number}) => void;
}

class SketchCanvas extends React.Component<
  SketchCanvasPropsWithTextMode,
  CanvasState
> {
  static defaultProps = {
    style: null,
    strokeColor: '#000000',
    strokeWidth: 3,
    onPathsChange: () => {},
    onStrokeStart: (_x: number, _y: number) => {},
    onStrokeChanged: () => {},
    onStrokeEnd: () => {},
    onShapeStart: (_x: number, _y: number) => {},
    onShapeChanged: () => {},
    onShapeEnd: () => {},
    onTextTapped: () => {},
    onTextEditingComplete: () => {},
    onSketchSaved: () => {},
    onShapesChange: () => {},
    user: null,

    touchEnabled: true,
    drawMode: 'draw',
    shapeFilled: false,
    shapes: [],

    text: null,
    localSourceImage: null,

    permissionDialogTitle: '',
    permissionDialogMessage: '',
  };

  _pathsToProcess: Path[];
  _paths: Path[];
  _path: PathData | null;
  _shapesToProcess: Shape[];
  _shapes: Shape[];
  _currentShape: ShapeData | null;
  _handle: any;
  _screenScale: number;
  _offset: {x: number; y: number};
  _size: {width: number; height: number};
  _initialized: boolean;
  panResponder: any;
  dimensionsSubscription: any;

  // Get the current device orientation
  getOrientation(): Orientation {
    const {width, height} = Dimensions.get('window');
    return width > height ? 'landscape' : 'portrait';
  }

  // Convert absolute coordinates to relative coordinates (for background image)
  absoluteToRelativeCoordinates(x: number, y: number) {
    // If there's no background image, return the original coordinates
    if (!this.props.localSourceImage) {
      return {x, y};
    }

    // Get the size of the canvas
    const canvasWidth = this._size.width;
    const canvasHeight = this._size.height;

    // Calculate relative coordinates (0-1 range)
    const relX = canvasWidth > 0 ? x / canvasWidth : 0;
    const relY = canvasHeight > 0 ? y / canvasHeight : 0;

    return {x: relX, y: relY};
  }

  // Check if a point is on a text element
  checkTextTap(x: number, y: number) {
    if (!this.props.text || this.props.text.length === 0) {
      return null;
    }

    // Check each text element
    for (const text of this.props.text) {
      // Get text position
      const textX = text.position?.x || 0;
      const textY = text.position?.y || 0;

      // Estimate text dimensions (this is a simple approximation)
      const textWidth = (text.text?.length || 0) * (text.fontSize || 20) * 0.6;
      const textHeight = (text.fontSize || 20) * 1.2;

      // Check if point is within text bounds
      if (
        x >= textX &&
        x <= textX + textWidth &&
        y >= textY - textHeight &&
        y <= textY
      ) {
        return text.id;
      }
    }

    return null;
  }

  state = {
    text: null,
    currentShape: null,
    isDrawingShape: false,
    selectedTextId: null,
    isMovingText: false,
    textMoveOffset: null,
    currentOrientation: 'portrait', // Default to portrait
    // Shape selection and manipulation
    selectedShapeId: null,
    isMovingShape: false,
    isResizingShape: false,
    resizeHandle: null, // 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'
    shapeMoveOffset: null,
  };

  // Get view manager config for native constants
  private static viewManager =
    Platform.OS === 'ios'
      ? NativeModules.RNSketchCanvasManager
      : UIManager[SketchViewName];

  // Helper function to get view manager config
  private getViewManagerConfig() {
    if (Platform.OS === 'ios') {
      return {
        Commands: {
          addPoint: 0,
          newPath: 1,
          deletePath: 2,
          addPath: 3,
          clear: 4,
          save: 5,
          endPath: 6,
          setShapeFilled: 7,
          drawShape: 8,
          deleteShape: 9,
        },
      };
    } else {
      return UIManager.getViewManagerConfig
        ? UIManager.getViewManagerConfig(SketchViewName)
        : UIManager[SketchViewName];
    }
  }

  static MAIN_BUNDLE: any =
    Platform.OS === 'ios'
      ? SketchCanvas.viewManager?.Constants?.MainBundlePath
      : '';
  static DOCUMENT: any =
    Platform.OS === 'ios'
      ? SketchCanvas.viewManager?.Constants?.NSDocumentDirectory
      : '';
  static LIBRARY: any =
    Platform.OS === 'ios'
      ? SketchCanvas.viewManager?.Constants?.NSLibraryDirectory
      : '';
  static CACHES: any =
    Platform.OS === 'ios'
      ? SketchCanvas.viewManager?.Constants?.NSCachesDirectory
      : '';

  constructor(props: SketchCanvasProps) {
    super(props);

    // Override the state to set the current orientation
    this.state = {
      ...this.state,
      currentOrientation: this.getOrientation(),
    };

    this._pathsToProcess = [];
    this._paths = [];
    this._path = null;
    this._shapesToProcess = [];
    this._shapes = props.shapes ? [...props.shapes] : [];
    this._currentShape = null;
    this._handle = null;
    this._screenScale = Platform.OS === 'ios' ? 1 : PixelRatio.get();
    this._offset = {x: 0, y: 0};
    this._size = {width: 0, height: 0};
    this._initialized = false;

    // Initialize the dimensions subscription to null
    this.dimensionsSubscription = null;

    this.panResponder = PanResponder.create({
      // Ask to be the responder:
      onStartShouldSetPanResponder: (_evt, _gestureState) => true,
      onStartShouldSetPanResponderCapture: (_evt, _gestureState) => true,
      onMoveShouldSetPanResponder: (_evt, _gestureState) => true,
      onMoveShouldSetPanResponderCapture: (_evt, _gestureState) => true,

      onPanResponderGrant: (evt, gestureState) => {
        // If pendingTextMode is enabled, add text at tap location and exit
        if (this.props.pendingTextMode && this.props.onTextPlaced) {
          const e = evt.nativeEvent;
          const x = e.locationX;
          const y = e.locationY;

          // Store relative coordinates if we have a background image
          if (this.props.localSourceImage) {
            const relCoords = this.absoluteToRelativeCoordinates(x, y);
            this.props.onTextPlaced({
              x,
              y,
              relX: relCoords.x,
              relY: relCoords.y,
            });
          } else {
            this.props.onTextPlaced({x, y});
          }
          return;
        }

        if (!this.props.touchEnabled) {
          return;
        }

        const e = evt.nativeEvent;
        const x = e.locationX;
        const y = e.locationY;

        // Convert to relative coordinates for background image reference
        const relCoords = this.absoluteToRelativeCoordinates(x, y);

        // Check if we're tapping on a text element
        if (this.props.text && this.props.text.length > 0) {
          // Check if tap is on a text element
          const tappedTextId = this.checkTextTap(x, y);
          if (tappedTextId !== null) {
            // Find the text object
            const textObj = this.props.text?.find(t => t.id === tappedTextId);
            if (textObj) {
              // Calculate offset between touch point and text position for dragging
              const offsetX = x - textObj.position.x;
              const offsetY = y - textObj.position.y;

              this.setState({
                selectedTextId: tappedTextId,
                isMovingText: true,
                textMoveOffset: {x: offsetX, y: offsetY},
              });
              this.props.onTextTapped?.(tappedTextId);
              return;
            }
          }
        }

        // Check if we're in select mode
        if (this.props.drawMode === 'select') {
          // Find the shape at the tap point
          const tappedShape = this.findShapeAtPoint(x, y);
          if (tappedShape && tappedShape.shape) {
            // Call the onShapeTapped callback
            this.props.onShapeTapped?.(tappedShape.shape.id);

            // Select the shape
            this.selectShape(tappedShape.shape.id);
            return;
          } else {
            // Deselect any selected shape if we tapped empty space
            this.deselectCurrentShape();
            return;
          }
        }

        // Check if we're interacting with a selected shape
        if (this.state.selectedShapeId !== null) {
          const shapeIndex = this._shapes.findIndex(
            s => s.shape && s.shape.id === this.state.selectedShapeId,
          );
          if (shapeIndex >= 0) {
            const shape = this._shapes[shapeIndex].shape;

            // Check if we're on a resize handle
            const handle = this.getResizeHandleAtPoint(x, y, shape);
            if (handle && shape.isResizable) {
              // Start resizing the shape
              const canResize = this.props.onShapeResizeStart?.(
                shape.id,
                handle as any,
              );

              // If onShapeResizeStart returns false, don't resize
              if (canResize === false) {
                return;
              }

              this.setState({
                isResizingShape: true,
                resizeHandle: handle,
                shapeMoveOffset: {x, y},
              });

              // Store the last move point for calculating deltas
              shape.lastMovePoint = {x, y};
              return;
            }

            // Check if we're on the shape itself
            if (this.isPointInShape(x, y, shape) && shape.isMovable) {
              // Start moving the shape
              const canMove = this.props.onShapeDragStart?.(shape.id);

              // If onShapeDragStart returns false, don't move
              if (canMove === false) {
                return;
              }

              this.setState({
                isMovingShape: true,
                shapeMoveOffset: {x, y},
              });

              // Store the last move point for calculating deltas
              shape.lastMovePoint = {x, y};
              return;
            }
          }
        }

        // If draw mode is 'none', don't draw anything
        if (this.props.drawMode === 'none') {
          return;
        }

        // Get native event for offset calculation
        const nativeEvent = evt.nativeEvent;
        this._offset = {
          x: nativeEvent.pageX - nativeEvent.locationX,
          y: nativeEvent.pageY - nativeEvent.locationY,
        };

        // Calculate touch position with offset
        const touchX = parseFloat(
          (gestureState.x0 - this._offset.x).toFixed(2),
        );
        const touchY = parseFloat(
          (gestureState.y0 - this._offset.y).toFixed(2),
        );

        // Calculate relative coordinates if we have a background image
        const touchRelCoords = this.absoluteToRelativeCoordinates(
          touchX,
          touchY,
        );

        // Handle different drawing modes
        if (this.props.drawMode === 'draw') {
          // Regular drawing mode
          this._path = {
            id: parseInt(String(Math.random() * 100000000), 10),
            color: this.props.strokeColor,
            width: this.props.strokeWidth,
            data: [],
            // Store relative coordinates for background image reference
            relativeData: [],
          };

          const viewManagerConfig = this.getViewManagerConfig();

          UIManager.dispatchViewManagerCommand(
            this._handle,
            viewManagerConfig.Commands.newPath,
            [
              this._path.id,
              processColor(this._path.color),
              this._path.width ? this._path.width * this._screenScale : 0,
            ],
          );

          UIManager.dispatchViewManagerCommand(
            this._handle,
            viewManagerConfig.Commands.addPoint,
            [
              parseFloat(
                (
                  Number((gestureState.x0 - this._offset.x).toFixed(2)) *
                  this._screenScale
                ).toString(),
              ),
              parseFloat(
                (
                  Number((gestureState.y0 - this._offset.y).toFixed(2)) *
                  this._screenScale
                ).toString(),
              ),
            ],
          );

          this._path.data.push(`${touchX},${touchY}`);
          // Also store relative coordinates
          this._path.relativeData?.push(
            `${touchRelCoords.x},${touchRelCoords.y}`,
          );
          this.props.onStrokeStart?.(touchX, touchY);
        } else if (
          ['line', 'rectangle', 'circle', 'arrow'].includes(
            this.props.drawMode || '',
          )
        ) {
          // Shape drawing mode - only for valid shape types
          this._currentShape = {
            id: parseInt(String(Math.random() * 100000000), 10),
            type: this.props.drawMode as ShapeType,
            color: this.props.strokeColor,
            width: this.props.strokeWidth,
            filled: this.props.shapeFilled,
            startPoint: {x: touchX, y: touchY},
            endPoint: {x: touchX, y: touchY},
            // Store relative coordinates for background image reference
            relativeStartPoint: {x: touchRelCoords.x, y: touchRelCoords.y},
            relativeEndPoint: {x: touchRelCoords.x, y: touchRelCoords.y},
          };

          this.setState({
            isDrawingShape: true,
            currentShape: {
              shape: this._currentShape,
              size: this._size,
              drawer: this.props.user,
              orientation: this.state.currentOrientation,
            },
          });

          this.props.onShapeStart?.(touchX, touchY);

          // Start drawing the shape on the canvas
          this.drawShapeOnCanvas(this._currentShape);
        }
        // If it's not 'draw' or a valid shape type, do nothing
      },

      onPanResponderMove: (evt, gestureState) => {
        if (!this.props.touchEnabled) {
          return;
        }

        // Handle text movement if we're in text moving mode
        if (
          this.state.isMovingText &&
          this.state.selectedTextId !== null &&
          this.state.textMoveOffset
        ) {
          const x =
            gestureState.moveX - this._offset.x - this.state.textMoveOffset.x;
          const y =
            gestureState.moveY - this._offset.y - this.state.textMoveOffset.y;

          // Call the text drag changed callback
          this.props.onTextDragChanged?.(this.state.selectedTextId, {x, y});

          return;
        }

        // Handle shape movement if we're in shape moving mode
        if (this.state.isMovingShape && this.state.selectedShapeId !== null) {
          const shapeIndex = this._shapes.findIndex(
            s => s.shape && s.shape.id === this.state.selectedShapeId,
          );
          if (shapeIndex >= 0) {
            const shape = this._shapes[shapeIndex].shape;

            // Calculate the new position
            const currentX = gestureState.moveX - this._offset.x;
            const currentY = gestureState.moveY - this._offset.y;

            // Calculate the delta from the last position
            const deltaX = currentX - (shape.lastMovePoint?.x || 0);
            const deltaY = currentY - (shape.lastMovePoint?.y || 0);

            // Update the shape position
            if (shape.startPoint && shape.endPoint) {
              shape.startPoint.x += deltaX;
              shape.startPoint.y += deltaY;
              shape.endPoint.x += deltaX;
              shape.endPoint.y += deltaY;

              // Update the last move point
              shape.lastMovePoint = {x: currentX, y: currentY};

              // Redraw the shape
              this.redrawShape(shape);

              // Call the shape drag changed callback
              this.props.onShapeDragChanged?.(shape.id, {
                x: shape.startPoint.x,
                y: shape.startPoint.y,
                width: shape.endPoint.x - shape.startPoint.x,
                height: shape.endPoint.y - shape.startPoint.y,
              });
            }
          }
          return;
        }

        // Handle shape resizing if we're in shape resizing mode
        if (
          this.state.isResizingShape &&
          this.state.selectedShapeId !== null &&
          this.state.resizeHandle
        ) {
          const shapeIndex = this._shapes.findIndex(
            s => s.shape && s.shape.id === this.state.selectedShapeId,
          );
          if (shapeIndex >= 0) {
            const shape = this._shapes[shapeIndex].shape;

            // Calculate the new position
            const currentX = gestureState.moveX - this._offset.x;
            const currentY = gestureState.moveY - this._offset.y;

            // Resize the shape based on which handle is being dragged
            if (shape.startPoint && shape.endPoint) {
              const handle = this.state.resizeHandle;

              if (handle === 'topLeft') {
                shape.startPoint.x = currentX;
                shape.startPoint.y = currentY;
              } else if (handle === 'topRight') {
                shape.endPoint.x = currentX;
                shape.startPoint.y = currentY;
              } else if (handle === 'bottomLeft') {
                shape.startPoint.x = currentX;
                shape.endPoint.y = currentY;
              } else if (handle === 'bottomRight') {
                shape.endPoint.x = currentX;
                shape.endPoint.y = currentY;
              }

              // Redraw the shape
              this.redrawShape(shape);

              // Call the shape resize changed callback
              this.props.onShapeResizeChanged?.(shape.id, {
                x: Math.min(shape.startPoint.x, shape.endPoint.x),
                y: Math.min(shape.startPoint.y, shape.endPoint.y),
                width: Math.abs(shape.endPoint.x - shape.startPoint.x),
                height: Math.abs(shape.endPoint.y - shape.startPoint.y),
              });
            }
          }
          return;
        }

        // Handle drawing if we're in drawing mode
        if (this.props.drawMode === 'draw' && this._path) {
          // Regular drawing mode
          const x = gestureState.moveX - this._offset.x;
          const y = gestureState.moveY - this._offset.y;

          // Calculate relative coordinates if we have a background image
          const relCoords = this.absoluteToRelativeCoordinates(x, y);

          const viewManagerConfig = this.getViewManagerConfig();

          UIManager.dispatchViewManagerCommand(
            this._handle,
            viewManagerConfig.Commands.addPoint,
            [
              parseFloat((x * this._screenScale).toString()),
              parseFloat((y * this._screenScale).toString()),
            ],
          );

          this._path.data.push(`${x},${y}`);
          // Also store relative coordinates
          this._path.relativeData?.push(`${relCoords.x},${relCoords.y}`);
          this.props.onStrokeChanged?.(x, y);
        } else if (
          this.state.isDrawingShape &&
          this._currentShape &&
          ['line', 'rectangle', 'circle', 'arrow'].includes(
            this.props.drawMode || '',
          )
        ) {
          // Shape drawing mode
          const x = gestureState.moveX - this._offset.x;
          const y = gestureState.moveY - this._offset.y;

          // Calculate relative coordinates if we have a background image
          const relCoords = this.absoluteToRelativeCoordinates(x, y);

          // Update the end point of the shape
          this._currentShape.endPoint = {x, y};
          this._currentShape.relativeEndPoint = {
            x: relCoords.x,
            y: relCoords.y,
          };

          // Update the current shape in state
          this.setState({
            currentShape: {
              shape: this._currentShape,
              size: this._size,
              drawer: this.props.user,
              orientation: this.state.currentOrientation,
            },
          });

          // Redraw the shape on the canvas
          this.redrawShape(this._currentShape);

          // Call the shape changed callback
          this.props.onShapeChanged?.(this._currentShape);
        }
      },

      onPanResponderRelease: (evt, gestureState) => {
        if (!this.props.touchEnabled) {
          return;
        }

        // Handle text movement end if we're in text moving mode
        if (this.state.isMovingText && this.state.selectedTextId !== null) {
          const x =
            gestureState.moveX -
            this._offset.x -
            (this.state.textMoveOffset?.x || 0);
          const y =
            gestureState.moveY -
            this._offset.y -
            (this.state.textMoveOffset?.y || 0);

          // Call the text drag end callback
          this.props.onTextDragEnd?.(this.state.selectedTextId, {x, y});

          // Reset the text moving state
          this.setState({
            isMovingText: false,
            textMoveOffset: null,
          });

          return;
        }

        // Handle shape movement end if we're in shape moving mode
        if (this.state.isMovingShape && this.state.selectedShapeId !== null) {
          const shapeIndex = this._shapes.findIndex(
            s => s.shape && s.shape.id === this.state.selectedShapeId,
          );
          if (shapeIndex >= 0) {
            const shape = this._shapes[shapeIndex].shape;

            // Call the shape drag end callback
            if (shape.startPoint && shape.endPoint) {
              this.props.onShapeDragEnd?.(shape.id, {
                x: shape.startPoint.x,
                y: shape.startPoint.y,
                width: shape.endPoint.x - shape.startPoint.x,
                height: shape.endPoint.y - shape.startPoint.y,
              });
            }

            // Reset the shape moving state
            this.setState({
              isMovingShape: false,
              shapeMoveOffset: null,
            });
          }
          return;
        }

        // Handle shape resizing end if we're in shape resizing mode
        if (this.state.isResizingShape && this.state.selectedShapeId !== null) {
          const shapeIndex = this._shapes.findIndex(
            s => s.shape && s.shape.id === this.state.selectedShapeId,
          );
          if (shapeIndex >= 0) {
            const shape = this._shapes[shapeIndex].shape;

            // Call the shape resize end callback
            if (shape.startPoint && shape.endPoint) {
              this.props.onShapeResizeEnd?.(shape.id, {
                x: Math.min(shape.startPoint.x, shape.endPoint.x),
                y: Math.min(shape.startPoint.y, shape.endPoint.y),
                width: Math.abs(shape.endPoint.x - shape.startPoint.x),
                height: Math.abs(shape.endPoint.y - shape.startPoint.y),
              });
            }

            // Reset the shape resizing state
            this.setState({
              isResizingShape: false,
              resizeHandle: null,
              shapeMoveOffset: null,
            });
          }
          return;
        }

        // Handle drawing end if we're in drawing mode
        if (this.props.drawMode === 'draw' && this._path) {
          // Regular drawing mode
          if (
            this._path.data.length > 0 ||
            this._path.relativeData?.length! > 0
          ) {
            const pathData = {
              id: this._path.id,
              color: this._path.color,
              width: this._path.width,
              data: this._path.data,
              relativeData: this._path.relativeData,
            };

            this._paths.push({
              drawer: this.props.user,
              size: this._size,
              path: pathData,
              orientation: this.state.currentOrientation,
            });

            this.props.onStrokeEnd?.({
              path: pathData,
              size: this._size,
              drawer: this.props.user,
              orientation: this.state.currentOrientation,
            });

            this.props.onPathsChange?.(this._paths.length);
          }
          this._path = null;
        } else if (
          this.state.isDrawingShape &&
          this._currentShape &&
          ['line', 'rectangle', 'circle', 'arrow'].includes(
            this.props.drawMode || '',
          )
        ) {
          // Shape drawing mode
          // Finalize the shape
          const shape = {
            shape: this._currentShape,
            size: this._size,
            drawer: this.props.user,
            orientation: this.state.currentOrientation,
          };

          // Add the shape to the shapes array
          this._shapes.push(shape);

          // Call the shape end callback
          this.props.onShapeEnd?.(shape);

          // Call the shapes change callback
          this.props.onShapesChange?.(this._shapes);

          // Reset the drawing shape state
          this.setState({
            isDrawingShape: false,
            currentShape: null,
          });

          this._currentShape = null;
        }
      },

      onPanResponderTerminate: (_evt, _gestureState) => {
        // Another component has become the responder, so this gesture
        // should be cancelled
      },
    });

    // Listen for device orientation changes
    this.dimensionsSubscription = Dimensions.addEventListener(
      'change',
      this.onDimensionsChange,
    );
  }

  componentDidMount() {
    this._initialized = true;
  }

  componentWillUnmount() {
    if (this.dimensionsSubscription) {
      this.dimensionsSubscription.remove();
    }
  }

  // Handle device orientation changes
  onDimensionsChange = () => {
    const newOrientation = this.getOrientation();
    if (newOrientation !== this.state.currentOrientation) {
      this.setState({currentOrientation: newOrientation});
    }
  };

  // Get the current device orientation
  getOrientation = (): Orientation => {
    const {width, height} = Dimensions.get('window');
    return width > height ? 'landscape' : 'portrait';
  };

  // Convert absolute coordinates to relative coordinates (for background image reference)
  absoluteToRelativeCoordinates = (x: number, y: number) => {
    if (
      !this.props.localSourceImage ||
      !this._size.width ||
      !this._size.height
    ) {
      return {x, y};
    }

    return {
      x: x / this._size.width,
      y: y / this._size.height,
    };
  };

  // Convert relative coordinates to absolute coordinates
  relativeToAbsoluteCoordinates = (relX: number, relY: number) => {
    if (!this._size.width || !this._size.height) {
      return {x: relX, y: relY};
    }

    return {
      x: relX * this._size.width,
      y: relY * this._size.height,
    };
  };

  // Check if a point is on a text element
  checkTextTap = (x: number, y: number) => {
    if (!this.props.text || this.props.text.length === 0) {
      return null;
    }

    // Check each text element
    for (const text of this.props.text) {
      // Skip if not draggable
      if (text.draggable === false) {
        continue;
      }

      // Get text dimensions (approximate)
      const textWidth = text.text.length * (text.fontSize || 20) * 0.6;
      const textHeight = (text.fontSize || 20) * 1.2;

      // Get anchor point (default to top-left)
      const anchorX = text.anchor?.x || 0;
      const anchorY = text.anchor?.y || 0;

      // Calculate text bounds based on anchor point
      const left = text.position.x - textWidth * anchorX;
      const top = text.position.y - textHeight * anchorY;
      const right = left + textWidth;
      const bottom = top + textHeight;

      // Check if point is within text bounds
      if (x >= left && x <= right && y >= top && y <= bottom) {
        return text.id;
      }
    }

    return null;
  };

  // Find a shape at a specific point
  findShapeAtPoint = (x: number, y: number) => {
    if (!this._shapes || this._shapes.length === 0) {
      return null;
    }

    // Check shapes in reverse order (last drawn on top)
    for (let i = this._shapes.length - 1; i >= 0; i--) {
      const shape = this._shapes[i];
      if (shape.shape && this.isPointInShape(x, y, shape.shape)) {
        return shape;
      }
    }

    return null;
  };

  // Check if a point is inside a shape
  isPointInShape = (x: number, y: number, shape: ShapeData) => {
    if (!shape.startPoint || !shape.endPoint) {
      return false;
    }

    const {startPoint, endPoint, type} = shape;

    // Normalize points (ensure startPoint is top-left, endPoint is bottom-right)
    const left = Math.min(startPoint.x, endPoint.x);
    const top = Math.min(startPoint.y, endPoint.y);
    const right = Math.max(startPoint.x, endPoint.x);
    const bottom = Math.max(startPoint.y, endPoint.y);
    const width = right - left;
    const height = bottom - top;

    // Check based on shape type
    switch (type) {
      case 'rectangle':
        // Simple bounding box check
        return x >= left && x <= right && y >= top && y <= bottom;

      case 'circle':
        // Distance from center check
        const centerX = (left + right) / 2;
        const centerY = (top + bottom) / 2;
        const radiusX = width / 2;
        const radiusY = height / 2;

        // Normalize point to account for ellipse
        const normalizedX = (x - centerX) / radiusX;
        const normalizedY = (y - centerY) / radiusY;

        // Check if point is inside ellipse
        return normalizedX * normalizedX + normalizedY * normalizedY <= 1;

      case 'line':
        // Check if point is close to the line
        const lineWidth = shape.width || 1;
        return this.isPointNearLine(
          x,
          y,
          startPoint.x,
          startPoint.y,
          endPoint.x,
          endPoint.y,
          lineWidth * 2,
        );

      case 'arrow':
        // Check if point is close to the arrow line
        const arrowWidth = shape.width || 1;
        return this.isPointNearLine(
          x,
          y,
          startPoint.x,
          startPoint.y,
          endPoint.x,
          endPoint.y,
          arrowWidth * 2,
        );

      default:
        return false;
    }
  };

  // Check if a point is near a line segment
  isPointNearLine = (
    px: number,
    py: number,
    x1: number,
    y1: number,
    x2: number,
    y2: number,
    tolerance: number,
  ) => {
    // Calculate the distance from point to line
    const A = px - x1;
    const B = py - y1;
    const C = x2 - x1;
    const D = y2 - y1;

    const dot = A * C + B * D;
    const lenSq = C * C + D * D;
    let param = -1;

    if (lenSq !== 0) {
      param = dot / lenSq;
    }

    let xx, yy;

    if (param < 0) {
      xx = x1;
      yy = y1;
    } else if (param > 1) {
      xx = x2;
      yy = y2;
    } else {
      xx = x1 + param * C;
      yy = y1 + param * D;
    }

    const dx = px - xx;
    const dy = py - yy;

    return Math.sqrt(dx * dx + dy * dy) <= tolerance;
  };

  // Get the resize handle at a specific point
  getResizeHandleAtPoint = (x: number, y: number, shape: ShapeData) => {
    if (!shape.startPoint || !shape.endPoint || !shape.isResizable) {
      return null;
    }

    // Normalize points
    const left = Math.min(shape.startPoint.x, shape.endPoint.x);
    const top = Math.min(shape.startPoint.y, shape.endPoint.y);
    const right = Math.max(shape.startPoint.x, shape.endPoint.x);
    const bottom = Math.max(shape.startPoint.y, shape.endPoint.y);

    // Handle size (make it easier to grab)
    const handleSize = 20;

    // Check each handle
    if (Math.abs(x - left) <= handleSize && Math.abs(y - top) <= handleSize) {
      return 'topLeft';
    }
    if (Math.abs(x - right) <= handleSize && Math.abs(y - top) <= handleSize) {
      return 'topRight';
    }
    if (
      Math.abs(x - left) <= handleSize &&
      Math.abs(y - bottom) <= handleSize
    ) {
      return 'bottomLeft';
    }
    if (
      Math.abs(x - right) <= handleSize &&
      Math.abs(y - bottom) <= handleSize
    ) {
      return 'bottomRight';
    }

    return null;
  };

  // Select a shape by ID
  selectShape = (shapeId: number) => {
    const shapeIndex = this._shapes.findIndex(
      s => s.shape && s.shape.id === shapeId,
    );
    if (shapeIndex >= 0) {
      const shape = this._shapes[shapeIndex];

      // Make the shape movable and resizable
      if (shape.shape) {
        shape.shape.isMovable = true;
        shape.shape.isResizable = true;
      }

      // Update the selected shape ID
      this.setState({selectedShapeId: shapeId});

      // Call the shape selected callback
      this.props.onShapeSelected?.(shape);
    }
  };

  // Deselect the current shape
  deselectCurrentShape = () => {
    if (this.state.selectedShapeId !== null) {
      const shapeIndex = this._shapes.findIndex(
        s => s.shape && s.shape.id === this.state.selectedShapeId,
      );
      if (shapeIndex >= 0) {
        const shape = this._shapes[shapeIndex];

        // Call the shape deselected callback
        this.props.onShapeDeselected?.(shape);
      }

      // Reset the selected shape ID
      this.setState({selectedShapeId: null});
    }
  };

  // Draw a shape on the canvas
  drawShapeOnCanvas = (shape: ShapeData) => {
    if (!shape.startPoint || !shape.endPoint) {
      return;
    }

    // Clear any existing shape with the same ID
    this.clearShapeFromCanvas(shape.id);

    // Draw the shape based on its type
    switch (shape.type) {
      case 'line':
        this.drawLine(shape);
        break;
      case 'rectangle':
        this.drawRectangle(shape);
        break;
      case 'circle':
        this.drawCircle(shape);
        break;
      case 'arrow':
        this.drawArrow(shape);
        break;
    }
  };

  // Redraw a shape on the canvas
  redrawShape = (shape: ShapeData) => {
    // Just call drawShapeOnCanvas which handles clearing and redrawing
    this.drawShapeOnCanvas(shape);
  };

  // Clear a shape from the canvas
  clearShapeFromCanvas = (shapeId: number) => {
    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.deleteShape,
      [shapeId],
    );
  };

  // Draw a line on the canvas
  drawLine = (shape: ShapeData) => {
    if (!shape.startPoint || !shape.endPoint) {
      return;
    }

    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.drawLine,
      [
        shape.id,
        processColor(shape.color),
        shape.width ? shape.width * this._screenScale : 1 * this._screenScale,
        shape.startPoint.x * this._screenScale,
        shape.startPoint.y * this._screenScale,
        shape.endPoint.x * this._screenScale,
        shape.endPoint.y * this._screenScale,
      ],
    );
  };

  // Draw a rectangle on the canvas
  drawRectangle = (shape: ShapeData) => {
    if (!shape.startPoint || !shape.endPoint) {
      return;
    }

    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.drawRectangle,
      [
        shape.id,
        processColor(shape.color),
        shape.width ? shape.width * this._screenScale : 1 * this._screenScale,
        shape.startPoint.x * this._screenScale,
        shape.startPoint.y * this._screenScale,
        shape.endPoint.x * this._screenScale,
        shape.endPoint.y * this._screenScale,
        shape.filled ? 1 : 0,
      ],
    );
  };

  // Draw a circle on the canvas
  drawCircle = (shape: ShapeData) => {
    if (!shape.startPoint || !shape.endPoint) {
      return;
    }

    // Calculate center and radius
    const centerX = (shape.startPoint.x + shape.endPoint.x) / 2;
    const centerY = (shape.startPoint.y + shape.endPoint.y) / 2;
    const radiusX = Math.abs(shape.endPoint.x - shape.startPoint.x) / 2;
    const radiusY = Math.abs(shape.endPoint.y - shape.startPoint.y) / 2;

    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.drawEllipse,
      [
        shape.id,
        processColor(shape.color),
        shape.width ? shape.width * this._screenScale : 1 * this._screenScale,
        centerX * this._screenScale,
        centerY * this._screenScale,
        radiusX * this._screenScale,
        radiusY * this._screenScale,
        shape.filled ? 1 : 0,
      ],
    );
  };

  // Draw an arrow on the canvas
  drawArrow = (shape: ShapeData) => {
    if (!shape.startPoint || !shape.endPoint) {
      return;
    }

    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.drawArrow,
      [
        shape.id,
        processColor(shape.color),
        shape.width ? shape.width * this._screenScale : 1 * this._screenScale,
        shape.startPoint.x * this._screenScale,
        shape.startPoint.y * this._screenScale,
        shape.endPoint.x * this._screenScale,
        shape.endPoint.y * this._screenScale,
      ],
    );
  };

  // Handle orientation changes
  componentDidMount() {
    // Add event listener for orientation changes
    this.dimensionsSubscription = Dimensions.addEventListener('change', () => {
      this.setState({currentOrientation: this.getOrientation()});
    });
  }

  componentWillUnmount() {
    // Remove the event listener when component unmounts
    if (this.dimensionsSubscription) {
      this.dimensionsSubscription.remove();
    }
  }

  render() {
    return (
      <RNSketchCanvas
        ref={ref => {
          this._handle = ref;
        }}
        style={this.props.style}
        onLayout={e => {
          this._size = {
            width: e.nativeEvent.layout.width,
            height: e.nativeEvent.layout.height,
          };
        }}
        {...this.panResponder.panHandlers}
        onChange={(e: any) => {
          if (e.nativeEvent.hasOwnProperty('pathsUpdate')) {
            this.props.onPathsChange?.(e.nativeEvent.pathsUpdate);
          } else if (
            e.nativeEvent.hasOwnProperty('success') &&
            e.nativeEvent.hasOwnProperty('path')
          ) {
            this.props.onSketchSaved?.(
              e.nativeEvent.success,
              e.nativeEvent.path,
            );
          } else if (e.nativeEvent.hasOwnProperty('success')) {
            this.props.onSketchSaved?.(e.nativeEvent.success, null);
          }
        }}
        localSourceImage={this.props.localSourceImage}
        permissionDialogTitle={this.props.permissionDialogTitle}
        permissionDialogMessage={this.props.permissionDialogMessage}
        text={this.props.text}
      />
    );
  }

  // Public methods

  // Clear the canvas
  clear() {
    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.clear,
      [],
    );
    this._paths = [];
    this._shapes = [];
    this._path = null;
    this._currentShape = null;
    this.setState({
      selectedShapeId: null,
      isMovingShape: false,
      isResizingShape: false,
      resizeHandle: null,
      shapeMoveOffset: null,
    });
  }

  // Undo the last action
  undo() {
    let lastId = -1;
    if (this._paths.length > 0) {
      lastId = this._paths.pop()?.path?.id || -1;
    } else if (this._shapes.length > 0) {
      const lastShape = this._shapes.pop();
      lastId = lastShape?.shape?.id || -1;
      if (lastId !== -1) {
        this.clearShapeFromCanvas(lastId);
      }
    }

    if (lastId !== -1) {
      UIManager.dispatchViewManagerCommand(
        this._handle,
        this.getViewManagerConfig().Commands.deletePath,
        [lastId],
      );

      return {
        id: lastId,
        type: this._paths.length > 0 ? 'path' : 'shape',
      };
    }

    return null;
  }

  // Add a path to the canvas
  addPath(data: PathData) {
    if (data.id && data.color && data.width && data.data) {
      this._paths.push({
        drawer: this.props.user,
        size: this._size,
        path: data,
        orientation: this.state.currentOrientation,
      });

      UIManager.dispatchViewManagerCommand(
        this._handle,
        this.getViewManagerConfig().Commands.addPath,
        [
          data.id,
          processColor(data.color),
          data.width * this._screenScale,
          data.data,
        ],
      );
    }
  }

  // Delete a path from the canvas
  deletePath(id: number) {
    const index = this._paths.findIndex(p => p.path && p.path.id === id);
    if (index > -1) {
      this._paths.splice(index, 1);
      UIManager.dispatchViewManagerCommand(
        this._handle,
        this.getViewManagerConfig().Commands.deletePath,
        [id],
      );
    }
  }

  // Save the canvas as an image
  save(
    imageType: string = 'png',
    transparent: boolean = false,
    folder: string = '',
    filename: string = String(Math.ceil(Math.random() * 100000000)),
    includeImage: boolean = true,
    includeText: boolean = true,
    cropToImageSize: boolean = false,
  ) {
    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.save,
      [
        imageType,
        transparent,
        folder,
        filename,
        includeImage,
        includeText,
        cropToImageSize,
      ],
    );
  }

  // Get the canvas as a base64 encoded string
  getBase64(
    imageType: string,
    transparent: boolean,
    includeImage: boolean,
    includeText: boolean,
    cropToImageSize: boolean,
    callback: (error: Error | null, result?: string) => void,
  ) {
    if (Platform.OS === 'ios') {
      SketchCanvasManager.transferToBase64(
        this._handle,
        imageType,
        transparent,
        includeImage,
        includeText,
        cropToImageSize,
        callback,
      );
    } else {
      NativeModules.RNSketchCanvasModule.transferToBase64(
        this._handle,
        imageType,
        transparent,
        includeImage,
        includeText,
        cropToImageSize,
        callback,
      );
    }
  }

  // Add a shape to the canvas
  addShape(shape: ShapeData) {
    if (shape.id && shape.type && shape.color && shape.width) {
      const newShape = {
        shape,
        size: this._size,
        drawer: this.props.user,
        orientation: this.state.currentOrientation,
      };

      this._shapes.push(newShape);
      this.drawShapeOnCanvas(shape);

      return shape.id;
    }

    return -1;
  }

  // Delete a shape from the canvas
  deleteShape(id: number) {
    const index = this._shapes.findIndex(s => s.shape && s.shape.id === id);
    if (index > -1) {
      this._shapes.splice(index, 1);
      this.clearShapeFromCanvas(id);
    }
  }

  // Update a shape on the canvas
  updateShape(shape: ShapeData) {
    const index = this._shapes.findIndex(
      s => s.shape && s.shape.id === shape.id,
    );
    if (index > -1) {
      this._shapes[index].shape = shape;
      this.redrawShape(shape);
    }
  }

  // Get all shapes on the canvas
  getShapes() {
    return this._shapes;
  }

  // Get all paths on the canvas
  getPaths() {
    return this._paths;
  }

  // Refresh the canvas
  refresh() {
    // Clear the canvas
    UIManager.dispatchViewManagerCommand(
      this._handle,
      this.getViewManagerConfig().Commands.clear,
      [],
    );

    // Redraw all paths
    for (const path of this._paths) {
      if (path.path) {
        this.addPath(path.path);
      }
    }

    // Redraw all shapes
    for (const shape of this._shapes) {
      if (shape.shape) {
        this.drawShapeOnCanvas(shape.shape);
      }
    }
  }
}

export default SketchCanvas;
