import React, {
  useImperativeHandle,
  useEffect,
  useRef,
  forwardRef,
  useState,
} from 'react';
import {
  SafeAreaView,
  findNodeHandle,
  DeviceEventEmitter,
  NativeEventEmitter,
  Platform,
  PixelRatio,
  StyleSheet,
  DimensionValue,
  Dimensions,
} from 'react-native';
import type {
  COORDINATE_TYPE,
  MAP_EVENT_LISTENER_TYPE,
  MAP_SIMULATION_REFERENCE_TYPE,
  MAP_VIEW_REFERENCE_TYPE,
  SIMULATION_COMMAND_TYPE,
} from './CommandTypes';
import type {
  INSTRUCTION_TYPE,
  JIBESTREAM_DESTINATION_TYPE,
  JIBESTREAM_PATH_TYPE,
  JIBESTREAM_STYLE_TYPE,
  JIBESTREAM_WAYPOINT_TYPE,
  MAP_CREDENTIALS_TYPE,
  MAP_OPTIONS_TYPE,
  MAP_TAP_EVENT_TYPE,
  SCREEN_ROTATED_EVENT_TYPE,
  JIBESTREAM_GEOFENCE_TYPE,
  MAP_IMAGE_TYPE,
  GEOJSON_PATH_TYPE,
  SIMULATION_STATUS_TYPE,
  SIMULATION_OPTIONS_TYPE,
  MAP_CENTER_OPTIONS_TYPE,
  MAP_CENTER_USER_OPTIONS_TYPE,
  MAP_ZOOM_BOUNDS_OPTIONS_TYPE,
  MAP_ZOOM_TO_PATH_OPTIONS_TYPE,
} from './JibestreamTypes';
import { MapViewManager, CTPEventEmitter } from './MapViewManager';
import {
  centerMap,
  createFragment,
  drawNavigationPath,
  setFollowBlueDot,
  setMapVenue,
  setUserPosition,
  clearWayfindingPath,
  wayfindToWaypoint,
  wayfindBetweenWaypoints,
  styleUnitsForDestination,
  addMapIcon,
  centerMapOnPoint,
  setMapViewBounds,
  setMapViewToPath,
} from './SendCommand';

import Compass from './Compass';
import { PathSimulator } from './PathSimulator';
import { generateGeojsonPath, preProcessPath } from './Instructions';

export interface MapViewProps {
  credentials: MAP_CREDENTIALS_TYPE;
  mapOptions?: MAP_OPTIONS_TYPE;
  style: { height: number; width: number; visible: boolean };
  compassStyle?: {
    size: number;
    right: DimensionValue;
    top: DimensionValue;
  };
  onMapMessage: MAP_EVENT_LISTENER_TYPE;
}

const MapView: React.ForwardRefRenderFunction<
  MAP_VIEW_REFERENCE_TYPE & MAP_SIMULATION_REFERENCE_TYPE,
  MapViewProps
> = (
  props: MapViewProps,
  parentRef:
    | React.Ref<MAP_VIEW_REFERENCE_TYPE & MAP_SIMULATION_REFERENCE_TYPE>
    | undefined
) => {
  const nodeRef = useRef(null);

  const compassStyle = StyleSheet.create({
    compass: {
      height: props.style.visible ? props?.compassStyle?.size ?? 0 : 0,
      width: props.style.visible ? props?.compassStyle?.size ?? 0 : 0,
      borderRadius: 5,
      alignItems: 'center',
      justifyContent: 'center',
      zIndex: 1,
      position: 'absolute',
      right: props?.compassStyle?.right,
      top: props?.compassStyle?.top,
      flex: 1,
      resizeMode: 'contain',
    },
  });

  const [compassTransform, setCompassTransform] = useState({
    rotate: '0deg',
  });

  // CALLBACK DATA
  const [onNavigationPathGeneratedValue, setOnNavigationPathGeneratedValue] =
    useState<{
      pathId: string;
      instructions: INSTRUCTION_TYPE[];
      start: JIBESTREAM_WAYPOINT_TYPE;
      end: JIBESTREAM_WAYPOINT_TYPE;
      jibestreamPath: JIBESTREAM_PATH_TYPE[];
    } | null>(null);
  const [
    onRerouteNavigationPathGeneratedValue,
    setOnRerouteNavigationPathGeneratedValue,
  ] = useState<{
    pathId: string;
    instructions: INSTRUCTION_TYPE[];
    start: JIBESTREAM_WAYPOINT_TYPE;
    end: JIBESTREAM_WAYPOINT_TYPE;
    jibestreamPath: JIBESTREAM_PATH_TYPE[];
  } | null>(null);
  const [onUserPositionUpdatedValue, setOnUserPositionUpdatedValue] = useState<
    number[] | null
  >(null);
  const [
    onSearchDestinationsGeneratedValue,
    setOnSearchDestinationsGeneratedValue,
  ] = useState<JIBESTREAM_DESTINATION_TYPE[] | null>(null);
  const [onMapTappedGenericValue, setOnMapTappedGenericValue] =
    useState<MAP_TAP_EVENT_TYPE | null>(null);
  const [onGeofencesChangedValue, setOnGeofencesChangedValue] = useState<{
    entered: boolean;
    geofences: JIBESTREAM_GEOFENCE_TYPE[];
  } | null>(null);
  const [onErrorValue, setOnErrorValue] = useState<string | null>(null);
  const [onVenueLoadedValue, setOnVenueLoadedValue] = useState<number | null>(
    null
  );

  // STATE VARIABLES
  // const [pathSimulator, _] = useState<PathSimulator>(new PathSimulator((loc: number[]) => {setUserPosition(findNodeHandle(nodeRef.current), loc[0]!, loc[1]!, 0, 5, null);}));

  const simRef = useRef<PathSimulator>(null);

  useEffect(() => {
    if (Platform.OS == 'android') {
      createFragment(findNodeHandle(nodeRef.current));
    }
    setTimeout(function () {
      setMapVenue({
        viewId: findNodeHandle(nodeRef.current),
        credentials: props.credentials,
        mapOptions: props.mapOptions,
      });
    }, 500);
  }, []);

  //   useEffect(() => {
  //     if (props.style.visible) {
  //       centerMap(findNodeHandle(nodeRef.current), {
  //         rotation: 0,
  //         zoom: 2,
  //         animationDuration: 100,
  //       });
  //     }
  //   }, [props.style.visible]);

  useEffect(() => {
    if (onNavigationPathGeneratedValue != null) {
      let pathId, instructions, start, end, jibestreamPath;
      ({ pathId, instructions, start, end, jibestreamPath } =
        onNavigationPathGeneratedValue);
      if (!jibestreamPath || !instructions) {
        props.onMapMessage.onNavigationPathGenerated?.(
          pathId,
          instructions,
          start,
          end,
          jibestreamPath,
          undefined
        );
        return;
      }
      const processedInstructions = preProcessPath(
        jibestreamPath,
        instructions
      );
      props.onMapMessage.onNavigationPathGenerated?.(
        pathId,
        processedInstructions,
        start,
        end,
        jibestreamPath,
        generateGeojsonPath(
          processedInstructions,
          jibestreamPath,
          props.mapOptions?.navigation?.directionsUnit ?? 'FEET',
          props.credentials?.locale ?? 'en-US'
        )
      );
    }
  }, [onNavigationPathGeneratedValue]);

  useEffect(() => {
    if (onRerouteNavigationPathGeneratedValue != null) {
      let pathId, instructions, start, end, jibestreamPath;
      ({ pathId, instructions, start, end, jibestreamPath } =
        onRerouteNavigationPathGeneratedValue);
      if (!jibestreamPath || !instructions) {
        props.onMapMessage.onRerouteNavigationPathGenerated?.(
          pathId,
          instructions,
          start,
          end,
          jibestreamPath,
          undefined
        );
        return;
      }
      const processedInstructions = preProcessPath(
        jibestreamPath,
        instructions
      );
      props.onMapMessage.onRerouteNavigationPathGenerated?.(
        pathId,
        processedInstructions,
        start,
        end,
        jibestreamPath,
        generateGeojsonPath(
          processedInstructions,
          jibestreamPath,
          props.mapOptions?.navigation?.directionsUnit ?? 'FEET',
          props.credentials?.locale ?? 'en-US'
        )
      );
    }
  }, [onRerouteNavigationPathGeneratedValue]);
  useEffect(() => {
    if (onUserPositionUpdatedValue != null)
      props.onMapMessage.onUserPositionUpdated?.(onUserPositionUpdatedValue);
  }, [onUserPositionUpdatedValue]);
  useEffect(() => {
    if (onSearchDestinationsGeneratedValue != null)
      props.onMapMessage.onSearchDestinationsGenerated?.(
        onSearchDestinationsGeneratedValue
      );
  }, [onSearchDestinationsGeneratedValue]);
  useEffect(() => {
    if (onMapTappedGenericValue != null)
      props.onMapMessage.onMapTappedGeneric?.(onMapTappedGenericValue);
  }, [onMapTappedGenericValue]);
  useEffect(() => {
    if (onErrorValue != null) props.onMapMessage.onError?.(onErrorValue);
  }, [onErrorValue]);
  useEffect(() => {
    if (onVenueLoadedValue != null)
      props.onMapMessage.onVenueLoaded?.(onVenueLoadedValue);
  }, [onVenueLoadedValue]);

  useEffect(() => {
    if (onGeofencesChangedValue != null) {
      let entered, geofences;
      ({ entered, geofences } = onGeofencesChangedValue);
      props.onMapMessage.onGeofencesChanged?.(entered, geofences);
    }
  }, [onGeofencesChangedValue]);

  useImperativeHandle(parentRef, () => {
    const viewId = findNodeHandle(nodeRef.current);
    const listeners: MAP_VIEW_REFERENCE_TYPE & MAP_SIMULATION_REFERENCE_TYPE = {
      setMapVenue: (
        credentials: MAP_CREDENTIALS_TYPE,
        mapOptions?: MAP_OPTIONS_TYPE
      ) => {
        setTimeout(function () {
          setMapVenue({
            viewId,
            credentials,
            mapOptions,
          });
        }, 500);
      },
      centerMapOnUser: (options: MAP_CENTER_USER_OPTIONS_TYPE) => {
        centerMap(viewId, {
          ...options,
          animationDuration: options.animationDuration ?? 1000,
        });
      },
      centerMapOnPoint: (options: MAP_CENTER_OPTIONS_TYPE) => {
        centerMapOnPoint(viewId, {
          ...options,
          animationDuration: options.animationDuration ?? 1000,
          format: options.format ?? 'LAT_LONG',
        });
      },
      setMapViewBounds: (options: MAP_ZOOM_BOUNDS_OPTIONS_TYPE) => {
        setMapViewBounds(viewId, {
          ...options,
          animationDuration: options.animationDuration ?? 1000,
          rotation: options.rotation ?? 0,
          format: options.format ?? 'LAT_LONG',
        });
      },
      setMapViewToPath: (options: MAP_ZOOM_TO_PATH_OPTIONS_TYPE) => {
        setMapViewToPath(viewId, {
          ...options,
          margin: options.margin ?? 0.2,
          animationDuration: options.animationDuration ?? 1000,
          rotation: options.rotation ?? 0,
        });
      },
      setFollowBlueDot: (
        enabled: boolean,
        rotate: boolean = false,
        interval: number = 500
      ) => {
        setFollowBlueDot(viewId, enabled, rotate, interval);
      },
      setUserPosition: (
        lat: number,
        long: number,
        heading: number,
        confidence: number
      ) => {
        setUserPosition(viewId, lat, long, heading, confidence, null);
      },
      setUserPositionXY: (
        x: number,
        y: number,
        heading: number,
        confidence: number
      ) => {
        setUserPosition(viewId, x, y, heading, confidence, 'xy');
      },
      clearWayfindingPath: () => {
        clearWayfindingPath(viewId);
      },
      wayfindToWaypoint: (waypointId: number) => {
        wayfindToWaypoint(viewId, waypointId);
      },
      wayfindBetweenWaypoints: (originId: number, destinationId: number) => {
        wayfindBetweenWaypoints(viewId, originId, destinationId);
      },
      drawNavigationPath: (
        pathId: string,
        {
          shouldFocus,
          style,
          originIcon,
          destinationIcon,
        }: {
          shouldFocus?: boolean;
          style?: JIBESTREAM_STYLE_TYPE;
          originIcon?: MAP_IMAGE_TYPE;
          destinationIcon?: MAP_IMAGE_TYPE;
        }
      ) => {
        drawNavigationPath(
          viewId,
          pathId,
          shouldFocus ?? true,
          style ?? {},
          originIcon,
          destinationIcon
        );
      },
      styleUnitsForDestination: (
        destinationId: number,
        style?: JIBESTREAM_STYLE_TYPE
      ) => {
        styleUnitsForDestination(viewId, destinationId, style);
      },
      addMapIcon: (
        coordinates: number[],
        coordType: COORDINATE_TYPE,
        icon: MAP_IMAGE_TYPE
      ) => {
        addMapIcon(viewId, coordinates, coordType, icon);
      },
      startPathSimulation: (
        path: GEOJSON_PATH_TYPE,
        options: SIMULATION_OPTIONS_TYPE
      ) => {
        simRef.current!.startPathSimulation(path, options);
      },
      controlSimulation: (
        command: SIMULATION_COMMAND_TYPE
      ): SIMULATION_STATUS_TYPE => {
        return simRef.current!.controlSimulation(command);
      },
      getSimulationStatus: (): SIMULATION_STATUS_TYPE => {
        return simRef.current!.getSimulationStatus();
      },
    };
    return listeners;
  });

  useEffect(() => {
    console.info('Registering Event Listeners');
    let eventEmitter: typeof DeviceEventEmitter | NativeEventEmitter | null =
      null;
    if (Platform.OS == 'android') {
      eventEmitter = DeviceEventEmitter;
    } else if (Platform.OS == 'ios') {
      eventEmitter = new NativeEventEmitter(CTPEventEmitter);
    }
    eventEmitter?.addListener(
      'NAVIGATION_PATH_GENERATED',
      (data: {
        pathId: string;
        instructions: INSTRUCTION_TYPE[];
        start: JIBESTREAM_WAYPOINT_TYPE;
        end: JIBESTREAM_WAYPOINT_TYPE;
        jibestreamPath: JIBESTREAM_PATH_TYPE[];
      }) => setOnNavigationPathGeneratedValue(data)
    );

    eventEmitter?.addListener(
      'REROUTE_NAVIGATION_PATH_GENERATED',
      (data: {
        pathId: string;
        instructions: INSTRUCTION_TYPE[];
        start: JIBESTREAM_WAYPOINT_TYPE;
        end: JIBESTREAM_WAYPOINT_TYPE;
        jibestreamPath: JIBESTREAM_PATH_TYPE[];
      }) => setOnRerouteNavigationPathGeneratedValue(data)
    );
    eventEmitter?.addListener(
      'USER_POSITION_UPDATED',
      (userPosition: number[]) => setOnUserPositionUpdatedValue(userPosition)
    );

    eventEmitter?.addListener(
      'GENERATED_SEARCH_DESTINATIONS',
      (destinations: JIBESTREAM_DESTINATION_TYPE[]) =>
        setOnSearchDestinationsGeneratedValue(destinations)
    );

    eventEmitter?.addListener(
      'MAP_TAPPED_GENERIC',
      (event: MAP_TAP_EVENT_TYPE) => setOnMapTappedGenericValue(event)
    );

    eventEmitter?.addListener(
      'SCREEN_ROTATED',
      (event: SCREEN_ROTATED_EVENT_TYPE) => {
        const newTransform = {
          rotate: Math.round(event.degrees).toString() + 'deg',
        };
        setCompassTransform(newTransform);
      }
    );

    eventEmitter?.addListener('ERROR', (msg: string) => setOnErrorValue(msg));

    eventEmitter?.addListener('VENUE_LOADED', (venueId: number) =>
      setOnVenueLoadedValue(venueId)
    );

    eventEmitter?.addListener(
      'GEOFENCES_CHANGED',
      (data: { entered: boolean; geofences: JIBESTREAM_GEOFENCE_TYPE[] }) =>
        setOnGeofencesChangedValue(data)
    );

    return () => {
      eventEmitter?.removeAllListeners('NAVIGATION_PATH_GENERATED');
      eventEmitter?.removeAllListeners('USER_POSITION_UPDATED');
      eventEmitter?.removeAllListeners('GENERATED_SEARCH_DESTINATIONS');
      eventEmitter?.removeAllListeners('MAP_TAPPED_GENERIC');
      eventEmitter?.removeAllListeners('ERROR');
      eventEmitter?.removeAllListeners('VENUE_LOADED');
      eventEmitter?.removeAllListeners('SCREEN_ROTATED');
      eventEmitter?.removeAllListeners('GEOFENCES_CHANGED');
      eventEmitter?.removeAllListeners('REROUTE_NAVIGATION_PATH_GENERATED');
    };
  }, []);

  return (
    <SafeAreaView
      style={{
        width: props.style.width,
        height: props.style.height,
        top: props.style.visible
          ? 0
          : -(2 * Dimensions.get('window').height + props.style.height + 5),
        left: props.style.visible
          ? 0
          : -(2 * Dimensions.get('window').width + props.style.width + 5),
        position: 'absolute',
        overflow: 'hidden',
      }}
    >
      <PathSimulator
        ref={simRef}
        updateUserLocation={(pos, heading) => {
          setUserPosition(
            findNodeHandle(nodeRef.current),
            pos[0]!,
            pos[1]!,
            heading,
            100,
            null
          );
        }}
        onSimStatusChanged={props.onMapMessage?.onSimulationStatusChanged}
      />
      <Compass
        style={[
          compassStyle.compass,
          {
            transform: [compassTransform],
          },
        ]}
      ></Compass>

      {MapViewManager ? (
        <MapViewManager
          style={
            Platform.OS == 'android'
              ? {
                  height: PixelRatio.getPixelSizeForLayoutSize(
                    props.style.height
                  ),
                  width: PixelRatio.getPixelSizeForLayoutSize(
                    props.style.width
                  ),
                }
              : {
                  height: props.style.height,
                  width: props.style.width,
                }
          }
          ref={nodeRef}
        />
      ) : null}
    </SafeAreaView>
  );
};

const mapViewFwRef = forwardRef(MapView);
export default mapViewFwRef;
