import { useEffect, useRef, useState } from "react";
import "mapbox-gl/dist/mapbox-gl.css";
import {
  Box,
  Button,
  DialogActions,
  DialogContent,
  DialogTitle,
  Grid2 as Grid,
  Icon,
  IconButton,
  LinearProgress,
  MenuItem,
  Select,
  Theme
} from "@mui/material";
import { HomeMarker } from "models/HomeMarker";
import { useGlobalState, useHomeMarkerAPI } from "providers";
import { useMobile, useSnackbar } from "hooks";
import { useCallback } from "react";
import HomeIcon from "products/AgIcons/HomeIcon";
//import { Theme } from "@material-ui/core/styles/createMuiTheme";
import { GpsFixed, Layers } from "@mui/icons-material";
//import Geocoder from "react-map-gl-geocoder";
import "maps/mapboxStyleOverrides.css";
import { pond } from "protobuf-ts/pond";
import MarkerMove from "products/AgIcons/MarkerMove";
import ResponsiveDialog from "common/ResponsiveDialog";
import Markers, { MarkerData } from "./mapMarkers/Markers";
import GeoMapLayer from "./mapLayers/geoMapLayer";
import Geocoder from "./mapControllers/Geocoder";
import { GeometryMapping, shapeFromCoords } from "models/GeometryMapping";
import { FeatureCollection, Feature } from "geojson";
import DrawController from "./mapControllers/drawController";
//import { Geometry } from "geojson";
import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox";
import { getDistanceUnit } from "utils";
import { MapMouseEvent } from "mapbox-gl";
import { makeStyles } from "@mui/styles";
import { Result } from "@mapbox/mapbox-gl-geocoder";

//const MAPBOX_TOKEN = process.env.REACT_APP_MAPBOX_ACCESS_TOKEN;
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

const mapStyleOptions = [
  {
    label: "Dark",
    value: "mapbox://styles/mapbox/dark-v10"
  },
  {
    label: "Satellite with streets",
    value: "mapbox://styles/mapbox/satellite-streets-v11"
  }
];

const useStyles = makeStyles((theme: Theme) => ({
    iconButtons: {
      background: theme.palette.background.default,
      margin: 5,
      boxShadow: "4px 4px 10px black"
    },
    pin: {
      borderRadius: "50rem",
      display: "inline-block",
      borderBottomRightRadius: "0",
      transform: "rotate(45deg)",
      cursor: "pointer",
      boxShadow: "4px 4px 10px black"
    },
    geoDot: {
      height: "25px",
      width: "25px",
      backgroundColor: "yellow",
      borderRadius: "50%",
      border: "5px solid black"
    },
    geoIconColor: {
      color: "black"
    },
    geoIconSearch: {
      animation: "$rotation 3s infinite"
    },
    "@keyframes rotation": {
      from: {
        transform: "rotate(0deg)"
      },
      to: {
        transform: "rotate(360deg)"
      }
    }
}));

export interface ViewData {
  longitude: number;
  latitude: number;
  zoom: number;
  transitionDuration?: number;
  xOffset?: number;
  yOffset?: number;
}

export interface MeasurementData {
  key: string;
  geoShape: string;
  coordinates: number[][];
  segmentDistanceKm: number[];
  totalDistanceKm: number;
}

interface Props {
  mapTools: JSX.Element;
  mapClick(mapClickEvent: MapMouseEvent): void;
  currentView: ViewData;
  ignoreHomeLoad?: boolean;
  defaultMapStyle?: string;
  layerOptions?: JSX.Element;
  markerData?: MarkerData[];
  displayMarkerTitles?: boolean;
  displayMarkerDetails?: boolean;
  customCursor?: JSX.Element;
  objectGeoData?: Map<string, pond.GeoData>;
  measurementData?: Map<string, MeasurementData>;
  objectTitles?: boolean;
  editorMode?: "drawPolygon" | "drawLine" | "drawPoint" | "edit" | "none" | "delete";
  placingMarker?: boolean;
  linePointLimit?: number;
  customSearchEntries?: Result[];
  drawMeasurement?: boolean;
  geocoderResultFunction?: (result: Result) => void;
  geocoderTransitionFunction?: (result: Result) => void;
  cutHoleInPolygon?: (object: string, coordinates: any[], geoType: string) => void;
  editGeoCallback?: (object: string, borders: pond.Shape[], holes?: pond.Shape[]) => void;
  addNewShape?: (coordinates: any[], geoType: string) => void;
  addNewMeasurement?: (coordinates: number[][], geoType: string) => void;
}

export default function MapBase(props: Props) {
  const [startingView, setStartingView] = useState<ViewData | undefined>();
  //const [viewport, setViewport] = useState<ViewData>(props.currentView);
  const transDuration = 3000; //in milliseconds
  const homeMarkerAPI = useHomeMarkerAPI();
  const classes = useStyles();
  const isMobile = useMobile();
  const [mapCursor, setMapCursor] = useState("auto");
  const [hcHovered, setHCHovered] = useState(false);
  const [gcHovered, setGCHovered] = useState(false);
  const [dcHovered, setDCHovered] = useState(false);
  const mapRef = useRef<MapRef>(null);
  const [haveHome, setHaveHome] = useState(false);
  const [homeKey, setHomeKey] = useState("");
  const [homePin, setHomePin] = useState<{ longitude: number; latitude: number }>();
  const [{ user, as }] = useGlobalState();
  const { openSnack } = useSnackbar();
  const [stateWatch, setStateWatch] = useState(0);
  const [watching, setWatching] = useState(false);
  const [locationFound, setLocationFound] = useState(true);
  const [geoPosition, setGeoPosition] = useState({ longitude: 0, latitude: 0 });
  const [dragAllowed, setDragAllowed] = useState(false);
  const [mapStyle, setMapStyle] = useState(
    props.defaultMapStyle ?? "mapbox://styles/mapbox/satellite-streets-v11"
  );
  const [styles, setStyles] = useState(false);
  const [msHovered, setMSHovered] = useState(false);
  const [layerIDs, setLayerIDs] = useState<string[]>([]);
  const [geoCollection, setGeoCollection] = useState<FeatureCollection>();
  const [measurementCollection, setMeasurementCollection] = useState<FeatureCollection>();
  const [groupSelect, setGroupSelect] = useState(false);
  const [currentZoom, setCurrentZoom] = useState(props.currentView.zoom);

  const movePos = () => {
    navigator.geolocation.getCurrentPosition(
      pos => {
        setGeoPosition({ longitude: pos.coords.longitude, latitude: pos.coords.latitude });
        setLocationFound(true);
        if (mapRef.current) {
          if (!mapRef.current.isMoving()) {
            mapRef.current.flyTo({
              center: [pos.coords.longitude, pos.coords.latitude],
              duration: transDuration,
              zoom: 18
            });
          }
        }
      },
      () => {
        //error callback function
      },
      { enableHighAccuracy: true }
    );
  };

  const geolocateToggle = () => {
    if (!watching) {
      setWatching(true);
      setStateWatch(navigator.geolocation.watchPosition(e => movePos()));
      setLocationFound(false);
    } else {
      navigator.geolocation.clearWatch(stateWatch);
      setWatching(false);
    }
  };

  //used to change the viewport from the parent
  useEffect(() => {
    //setViewport(props.currentView);
    if (mapRef.current) {
      let xOffset = props.currentView.xOffset ?? 0;
      let yOffset = props.currentView.yOffset ?? 0;
      mapRef.current.flyTo({
        offset: [xOffset, yOffset], //this is in pixels
        center: [props.currentView.longitude, props.currentView.latitude], //this is in degrees
        duration: props.currentView.transitionDuration,
        zoom: props.currentView.zoom
      });
    }
  }, [props.currentView]);

  const homeControl = () => {
    if (!haveHome) {
      setHaveHome(true);
      if (mapRef.current) {
        setHomePin({
          longitude: mapRef.current.getCenter().lng,
          latitude: mapRef.current.getCenter().lat
        });
      }

      // save the pin to the database
      saveHomeMarker();
    } else {
      if (mapRef.current && homePin) {
        mapRef.current.flyTo({
          center: [homePin.longitude, homePin.latitude],
          duration: transDuration
        });
      }
    }
  };

  const loadHomeMarkers = useCallback(() => {
    homeMarkerAPI
      .listHomeMarkers(1, 0, undefined, undefined, undefined, undefined, as)
      .then(resp => {
        if (resp.data.homeMarker.length < 1) {
          setHomePin(undefined);
          setHaveHome(false);
          setStartingView(props.currentView);
          return;
        }
        let hm = HomeMarker.any(resp.data.homeMarker[0]);
        setHomeKey(hm.key());
        setHomePin({
          longitude: hm.long(),
          latitude: hm.lat()
        });
        setHaveHome(true);
        if (props.ignoreHomeLoad) {
          setStartingView(props.currentView);
        } else if (!startingView) {
          setStartingView({
            longitude: hm.long(),
            latitude: hm.lat(),
            zoom: user.settings.mapZoom
          });
        } else if (mapRef.current) {
          mapRef.current.flyTo({
            center: [hm.long(), hm.lat()],
            zoom: user.settings.mapZoom,
            duration: 0
          });
        }
      })
      .catch(err => {
        openSnack("Could not load Home Pin");
      });
  }, [as, homeMarkerAPI, openSnack, props.ignoreHomeLoad, user]); // eslint-disable-line react-hooks/exhaustive-deps

  const saveHomeMarker = () => {
    if (mapRef.current) {
      let hm = HomeMarker.create();
      hm.settings.userId = as ? as : user.id();
      hm.settings.longitude = mapRef.current.getCenter().lng;
      hm.settings.latitude = mapRef.current.getCenter().lat;
      homeMarkerAPI
        .addHomeMarker(hm.settings, as)
        .then(resp => {
          setHomeKey(resp.data.homeMarker);
          openSnack("New Home Marker Created");
        })
        .catch(err => openSnack("Failed to save new Home Marker"));
    }
  };

  const updateHomeMarker = (longitude: number, latitude: number) => {
    let hm = HomeMarker.create();
    if (homePin) {
      hm.settings.latitude = latitude;
      hm.settings.longitude = longitude;
      hm.settings.userId = as ? as : user.id();
      homeMarkerAPI
        .updateHomeMarker(homeKey, hm.settings, undefined, as)
        .then(resp => {
          openSnack("Home Marker Moved");
        })
        .catch(err => openSnack("Failed to Update Home Marker"));
    }
  };

  useEffect(() => {
    loadHomeMarkers();
  }, [loadHomeMarkers, as]);

  const homeDragEnd = (event: MarkerDragEvent) => {
    let longitude = event.lngLat.lng;
    let latitude = event.lngLat.lat;
    setHomePin({ longitude, latitude });
    // update the pin in the database
    updateHomeMarker(longitude, latitude);
  };

  const tools = () => {
    return (
      <Box>
        <Box
          style={{
            marginLeft: 10,
            position: "absolute",
            top: isMobile ? "120px" : "7%",
            left: isMobile ? 0 : 75
          }}
          className="mapboxgl-ctrl-top-left mapboxgl-ctrl">
          {props.mapTools}
        </Box>
        <Box
          style={{
            marginRight: 10,
            position: "absolute",
            bottom: isMobile ? "7%" : "3%",
            right: 10
          }}
          className="mapboxgl-ctrl-bottom-right mapboxgl-ctrl">
          <Box>
            {dragAllowed && (
              <IconButton
                className={classes.iconButtons}
                style={
                  groupSelect ? { background: "yellow" } : dcHovered ? { background: "grey" } : {}
                }
                title="Group Select"
                onClick={() => setGroupSelect(!groupSelect)}
                onMouseOver={() => setDCHovered(true)}
                onMouseOut={() => setDCHovered(false)}>
                <MarkerMove />
              </IconButton>
            )}
          </Box>
          <Grid container direction={isMobile ? "column" : "row"}>
            <Grid>
              <IconButton
                className={classes.iconButtons}
                style={
                  dragAllowed ? { background: "yellow" } : dcHovered ? { background: "grey" } : {}
                }
                title="Toggle Drag"
                onClick={() => {
                  setGroupSelect(false);
                  setDragAllowed(!dragAllowed);
                }}
                onMouseOver={() => setDCHovered(true)}
                onMouseOut={() => setDCHovered(false)}>
                <MarkerMove />
              </IconButton>
            </Grid>
            <Grid>
              <IconButton
                className={classes.iconButtons}
                style={msHovered ? { background: "grey" } : {}}
                title="Map Style"
                onClick={() => setStyles(true)}
                onMouseOver={() => setMSHovered(true)}
                onMouseOut={() => setMSHovered(false)}>
                <Layers />
              </IconButton>
            </Grid>
            <Grid>
              <IconButton
                className={classes.iconButtons}
                style={hcHovered ? { background: "grey" } : {}}
                title="Home Pin"
                onClick={homeControl}
                onMouseOver={() => setHCHovered(true)}
                onMouseOut={() => setHCHovered(false)}>
                <HomeIcon />
              </IconButton>
            </Grid>
            <Grid>
              <IconButton
                className={classes.iconButtons}
                style={
                  watching ? { background: "yellow" } : gcHovered ? { background: "grey" } : {}
                }
                title="Find Me"
                onClick={geolocateToggle}
                onMouseOver={() => setGCHovered(true)}
                onMouseOut={() => setGCHovered(false)}>
                <Icon className={locationFound ? "" : classes.geoIconSearch}>
                  <GpsFixed
                    classes={{
                      colorSecondary: classes.geoIconColor
                    }}
                    color={watching ? "secondary" : "inherit"}
                  />
                </Icon>
              </IconButton>
            </Grid>
          </Grid>
        </Box>
      </Box>
    );
  };

  const mapClick = (event: MapMouseEvent) => {
    if (!watching) {
      props.mapClick(event);
    }
  };

  const mapStyleDialog = () => {
    return (
      <ResponsiveDialog open={styles} onClose={() => setStyles(false)}>
        <DialogTitle>Select Map Style</DialogTitle>
        <DialogContent>
          <Select
            fullWidth
            label="Style"
            value={mapStyle}
            onChange={e => setMapStyle(e.target.value as string)}>
            {mapStyleOptions.map(op => (
              <MenuItem key={op.value} value={op.value}>
                {op.label}
              </MenuItem>
            ))}
          </Select>
          {/* layer options from the object controller */}
          {props.layerOptions}
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setStyles(false)}>Close</Button>
        </DialogActions>
      </ResponsiveDialog>
    );
  };

  const onMouseEnter = useCallback(() => setMapCursor("pointer"), []);
  const onMouseLeave = useCallback(() => setMapCursor("auto"), []);

  useEffect(() => {
    if (props.editorMode === "drawPolygon" || props.placingMarker) {
      setMapCursor("crosshair");
    } else {
      setMapCursor("auto");
    }
  }, [props.editorMode, props.placingMarker]);

  useEffect(() => {
    if (props.objectGeoData) {
      let feats: Feature[] = [];
      props.objectGeoData.forEach(data => {
        let newFeature = GeometryMapping.geoJSON(data.geoShape, data.shapes, data.holes) as Feature;
        newFeature.id = data.objectKey;
        newFeature.properties = {
          title: data.title,
          objectKey: data.objectKey,
          fill: data.colour,
          lineWidth: data.geoShape === "LineString" ? 5 : 2,
          origin: data.origin
        };
        feats.push(newFeature);
      });
      let collection: FeatureCollection = {
        type: "FeatureCollection",
        features: feats
      };
      setGeoCollection(collection);
    }
  }, [props.objectGeoData]); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    //add the measurementdata into the feature collection to be passed to our geo map layer
    if (props.measurementData) {
      let feats: Feature[] = [];
      props.measurementData.forEach(data => {
        let newFeature = GeometryMapping.geoJSON(data.geoShape, [
          shapeFromCoords(data.coordinates)
        ]) as Feature;
        newFeature.id = data.key;
        newFeature.properties = {
          lineWidth: 5,
          origin: pond.DataOrigin.DATA_ORIGIN_ADAPTIVE,
          totalDist:
            getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
              ? (data.totalDistanceKm * 1000).toFixed(2)
              : (data.totalDistanceKm * 3280.8398950131).toFixed(2)
        };
        feats.push(newFeature);
      });
      let collection: FeatureCollection = {
        type: "FeatureCollection",
        features: feats
      };
      setMeasurementCollection(collection);
    }
  }, [props.measurementData]);

  const onUpdate = (event: any) => {
    let borders: pond.Shape[] = [];
    let holes: pond.Shape[] = [];
    let objectKey: string = "";
    if (event.action === "change_coordinates" && event.features) {
      let feature = event.features[0];
      if (feature) {
        let coords: any[] = feature.geometry.coordinates;
        objectKey = feature.id;
        if (feature.geometry.type === "Polygon") {
          coords.forEach((poly: any[], i) => {
            //the first shape is the border
            if (i === 0) {
              borders.push(shapeFromCoords(poly));
            } else {
              holes.push(shapeFromCoords(poly));
            }
          });
        } else if (feature.geometry.type === "LineString") {
          borders.push(shapeFromCoords(coords));
        }
      }
    }
    if (props.editGeoCallback) {
      props.editGeoCallback(objectKey, borders, holes.length > 0 ? holes : undefined);
    }
  };

  const onCreate = (event: any, objectToCut?: string, measurement?: boolean) => {
    let feature = event.features[0];
    //console.log(feature)
    //if the string exist we are adding a hole to an existing object
    if (objectToCut) {
      if (props.cutHoleInPolygon) {
        props.cutHoleInPolygon(objectToCut, feature.geometry.coordinates, feature.geometry.type);
      }
    } else if (measurement) {
      if (props.addNewMeasurement) {
        props.addNewMeasurement(feature.geometry.coordinates, feature.geometry.type);
      }
    } else {
      if (props.addNewShape) {
        //console.log("add new shape")
        props.addNewShape(feature.geometry.coordinates, feature.geometry.type);
      }
    }
  };

  const getEditableFeatures = () => {
    let editableFeatures: FeatureCollection = {
      type: "FeatureCollection",
      features: []
    };
    if (geoCollection) {
      geoCollection.features.forEach(feature => {
        //if the origin of the data comes from us (ie. a field/construction site with the adaptive origin)
        if (
          feature.properties &&
          feature.properties.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE
        ) {
          editableFeatures.features.push(feature);
        }
      });
    }
    return editableFeatures;
  };

  return startingView ? (
    <Box height="100%">
      {tools()}
      {mapStyleDialog()}
      <Map
        style={{width: "100%", height: "100%"}}
        ref={mapRef}
        maxZoom={18}
        initialViewState={startingView}
        // {...viewport}
        mapStyle={mapStyle}
        onZoomEnd={e => {
          setCurrentZoom(e.viewState.zoom);
        }}
        onMouseEnter={() => {
          if (
            (props.editorMode === "none" || props.editorMode === undefined) &&
            !props.placingMarker &&
            !props.drawMeasurement
          )
            onMouseEnter();
        }}
        onMouseLeave={() => {
          if (
            (props.editorMode === "none" || props.editorMode === undefined) &&
            !props.placingMarker &&
            !props.drawMeasurement
          )
            onMouseLeave();
        }}
        interactiveLayerIds={layerIDs}
        // onMove={e => {
        //   setViewport({
        //     longitude: e.viewState.longitude,
        //     latitude: e.viewState.latitude,
        //     zoom: e.viewState.zoom
        //   });
        // }}
        mapboxAccessToken={MAPBOX_TOKEN}
        cursor={mapCursor}
        onClick={e => {
          mapClick(e);
        }}
        doubleClickZoom={false}>
        {/* map controllers */}
        {MAPBOX_TOKEN && (
          <Geocoder
            mapboxAccessToken={MAPBOX_TOKEN}
            position="top-right"
            customEntries={props.customSearchEntries}
            resultFunction={props.geocoderResultFunction}
            customTransition={props.geocoderTransitionFunction}
          />
        )}
        <DrawController
          featureCollection={getEditableFeatures()}
          editMode={props.editorMode}
          onCreate={onCreate}
          onUpdate={onUpdate}
          measurement={props.drawMeasurement}
          linePointLimit={props.linePointLimit}
        />
        {/* markers */}
        {props.markerData && (
          <Markers
            markerData={props.markerData}
            dragOn={dragAllowed}
            groupSelect={groupSelect}
            mapZoomLevel={currentZoom}
            displayDetails={props.displayMarkerDetails}
            showMarkerTitle={props.displayMarkerTitles}
          />
        )}

        {watching && locationFound && (
          <Marker longitude={geoPosition.longitude} latitude={geoPosition.latitude}>
            <Box className={classes.geoDot} />
          </Marker>
        )}
        {haveHome && homePin && (
          <Marker
            longitude={homePin.longitude}
            latitude={homePin.latitude}
            draggable
            onDragEnd={homeDragEnd}
            offset={[0, -30]}>
            <Box
              className={classes.pin}
              style={{ pointerEvents: "none", width: 50, height: 50, background: "red" }}>
              <Box
                style={{
                  transform: "rotate(-45deg)",
                  width: 30,
                  height: 30,
                  marginTop: 10,
                  marginLeft: 7,
                  pointerEvents: "none"
                }}>
                <HomeIcon type="light" height={30} width={30} />
              </Box>
            </Box>
          </Marker>
        )}
        {/* map layers */}
        {geoCollection && (
          <GeoMapLayer
            objectCollection={geoCollection}
            measurementCollection={measurementCollection}
            showTitle={props.objectTitles}
            setInteractiveLayers={e => {
              setLayerIDs(e);
            }}
          />
        )}
      </Map>
    </Box>
  ) : (
    <Box>
      <LinearProgress />
    </Box>
  );
}