import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import GoogleMapReact from 'google-map-react';

import Box from '../Box';
import { withTheme } from '../styles';

import { Marker, getPolyline, getTransportIcon } from './children';
import mapStyles from './Map.style';

const VMap = (props) => {
  const {
    apiKey,
    defaultCenter,
    defaultZoom,
    hideControls,
    highlights,
    language,
    settings,
    transportation,
  } = props;

  const [mapLoaded, setMapLoaded] = useState(false);
  const [mapData, setMapData] = useState({
    map: null,
    maps: null,
    bounds: null,
    init: false,
    defaultZoom: 0,
    currentZoom: 0,
    defaultCenter,
    transportation: [],
  });

  const mapOptions = {
    disableDefaultUI: hideControls,
    scrollWheel: false,
  };

  const mapURLKeys = {
    key: apiKey,
    language,
    libraries: 'geometry',
    force: 'lite',
  };

  /* istanbul ignore next */
  const getTransportationElements = () => {
    return transportation.map((transport, index) => {
      const transportKey = `transportation-line-${index}`;
      const transportIconKey = `transportation-icon-${index}`;

      return {
        route: getPolyline(
          transportKey,
          mapData.maps,
          mapData.map,
          transport.path,
        ),
        icon: getTransportIcon(
          transportIconKey,
          transport.path,
          mapData.maps,
          transport.icon || transport.type,
        ),
      };
    });
  };

  /* istanbul ignore next */
  const loadMap = () => {
    setMapData({
      ...mapData,
      transportation: getTransportationElements(),
    });
  };

  /* istanbul ignore next */
  const handleGoogleMapApi = ({ map, maps }) => {
    setMapData({ ...mapData, map, maps, bounds: new maps.LatLngBounds() });
    setMapLoaded(true);
    if (map) {
      map.setOptions({ styles: mapStyles });
    }
  };

  /* istanbul ignore next */
  useEffect(() => {
    if (mapLoaded) {
      loadMap();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mapLoaded]);

  /* istanbul ignore next */
  const markers = () => {
    const points = highlights.map((marker, index) => {
      const { lat } = marker.coordinates;
      const { lng } = marker.coordinates;

      const location = new mapData.maps.LatLng(lat, lng);
      mapData.bounds.extend(location);

      const markerKey = `marker-${index}`;
      return (
        <Marker
          key={markerKey}
          title={marker.title}
          label={marker.label}
          isOnlyMarker={marker.isOnlyMarker}
          lat={lat}
          lng={lng}
          zoomLevel={mapData.currentZoom}
          defaultZoom={mapData.defaultZoom}
        />
      );
    });

    if (!mapData.init) {
      mapData.map.fitBounds(mapData.bounds);
      mapData.map.panToBounds(mapData.bounds);

      setMapData({
        ...mapData,
        init: true,
        defaultZoom:
          mapData.map.zoom > 10 ? mapData.map.zoom - 1 : mapData.map.zoom + 1,
      });
    }

    return points;
  };

  /* istanbul ignore next */
  const onMapChange = ({ zoom }) => {
    if (mapLoaded) {
      setMapData({ ...mapData, currentZoom: zoom });
    }
  };

  /* istanbul ignore next */
  const printMap = () => {
    return (
      <GoogleMapReact
        defaultZoom={defaultZoom}
        defaultCenter={mapData.defaultCenter}
        onGoogleApiLoaded={handleGoogleMapApi}
        yesIWantToUseGoogleMapApiInternals
        options={mapOptions}
        bootstrapURLKeys={mapURLKeys}
        onChange={(ev) => onMapChange(ev)}
      >
        {mapLoaded && markers()}
        {mapLoaded &&
          mapData.transportation &&
          mapData.transportation.map((transport) => transport.icon)}
        {mapLoaded &&
          mapData.transportation &&
          mapData.transportation.map((transport) => transport.route)}
      </GoogleMapReact>
    );
  };

  return <Box {...settings}>{printMap()}</Box>;
};

VMap.propTypes = {
  apiKey: PropTypes.string.isRequired,
  hideControls: PropTypes.bool,
  language: PropTypes.string.isRequired,
  highlights: PropTypes.arrayOf(Object),
  transportation: PropTypes.arrayOf(Object),
  settings: PropTypes.objectOf(Object),
  defaultCenter: PropTypes.arrayOf(PropTypes.number),
  defaultZoom: PropTypes.number,
};

VMap.defaultProps = {
  settings: {},
  highlights: [],
  transportation: [],
  hideControls: false,
  defaultCenter: [0, 0],
  defaultZoom: 4,
};

VMap.displayName = 'Map';

export default withTheme(VMap);
