import {
  TextField,
  DialogContent,
  DialogActions,
  Button,
  Box,
  DialogTitle,
  Autocomplete
} from "@mui/material";
import React, { useCallback, useState } from "react";
import "mapbox-gl/dist/mapbox-gl.css";
import { pond } from "protobuf-ts/pond";
import { useDeviceAPI, useGroupAPI, useMobile, useSnackbar } from "hooks";
import { useEffect } from "react";
import { useGlobalState, useSiteAPI } from "providers";
import ResponsiveDialog from "common/ResponsiveDialog";
import { Device, Group, Site } from "models";
// import GroupSettingsIcon from "@material-ui/icons/Settings";
import MapBase, { ViewData } from "maps/MapBase";
import ConstructionMapTools from "maps/mapObjectTools/constructionMapTools";
import { MarkerData } from "maps/mapMarkers/Markers";
import { clone } from "lodash";
import NexusSTIcon from "products/Construction/NexusSTIcon";
import DeviceDrawer from "maps/mapDrawers/DeviceDrawer";
//import { Autocomplete } from "@material-ui/lab";
import JobsiteIcon from "products/Construction/JobSiteIcon";
import SiteDrawer from "maps/mapDrawers/SiteDrawer";
import DeviceGroupIcon from "products/Construction/DeviceGroupIcon";
import GroupDrawer from "maps/mapDrawers/GroupDrawer";
import { Result } from "@mapbox/mapbox-gl-geocoder";
//import { GeocoderObject } from "maps/mapControllers/Geocoder";

interface Props {
  setSiteCallback?: React.Dispatch<React.SetStateAction<pond.SiteSettings | undefined>>;
  disableDrawer?: boolean;
  startingView?: ViewData;
}

export default function ConstructionMapController(props: Props) {
  const transDuration = 3000;
  const [{ user, as }] = useGlobalState();
  const [currentView, setCurrentView] = useState<ViewData>(
    props.startingView
      ? props.startingView
      : {
          latitude: 52.1579,
          longitude: -106.6702,
          zoom: user.settings.mapZoom
        }
  );
  const [newLong, setNewLong] = useState(0);
  const [newLat, setNewLat] = useState(0);
  const [siteName, setSiteName] = useState("");
  const siteAPI = useSiteAPI();
  const [openSiteDrawer, setOpenSiteDrawer] = useState(false);
  const [SiteDialog, setSiteDialog] = useState(false);
  const [sites, setSites] = useState<Map<string, Site>>(new Map());
  const { openSnack } = useSnackbar();
  const deviceAPI = useDeviceAPI();
  const [loadingSites, setLoadingSites] = useState(false);
  const [loadingDevices, setLoadingDevices] = useState(false);
  //map that is the site id as the key with and array of heater data objects (heaters for that site)
  const [heaterMap, setHeaterMap] = useState<Map<string, pond.ObjectHeaterData[]>>(
    new Map<string, pond.ObjectHeaterData[]>()
  );
  const groupAPI = useGroupAPI();
  const [groups, setGroups] = useState<Map<string, Group>>(new Map());
  const [openMarkerDialog, setOpenMarkerDialog] = useState(false);
  const [selectKey, setSelectKey] = useState("");
  const [endTime, setEndTime] = useState(0);
  const [devOps, setDevOps] = useState<Device[]>([]);
  const [groupOps, setGroupOps] = useState<Group[]>([]);
  const [devices, setDevices] = useState<Map<string, Device>>(new Map<string, Device>());
  const [openDevDrawer, setOpenDevDrawer] = useState(false);
  const [openGroupDrawer, setOpenGroupDrawer] = useState(false);
  const [markerType, setMarkerType] = useState<pond.ObjectType>(0);
  const [devMarkerData, setDevMarkerData] = useState<Map<string, MarkerData>>(new Map());
  const [siteMarkerData, setSiteMarkerData] = useState<Map<string, MarkerData>>(new Map());
  const [groupMarkerData, setGroupMarkerData] = useState<Map<string, MarkerData>>(new Map());
  const [objectKey, setObjectKey] = useState<string>("");
  const isMobile = useMobile();
  const [markers, setMarkers] = useState<MarkerData[]>([]);
  const [displayMarkerTitle, setDisplayMarkerTitle] = useState(false);
  const zoomLevels = {
    far: 14,
    close: 18
  };
  const [customGeoSearchEntries, setCustomGeoSearchEntries] = useState<Result[]>([]);
  const [siteSearchEntries, setSiteSearchEntries] = useState<Result[]>([]);
  const [deviceSearchEntries, setDeviceSearchEntries] = useState<Result[]>([]);
  const [groupSearchEntries, setGroupSearchEntries] = useState<Result[]>([]);

  //this click delay is implemented to fix an issue with touch screens that caused the drawer to close immediately after opening
  const clickDelay = () => {
    let endTime = new Date().valueOf() + 1000;
    setEndTime(endTime);
  };

  const closeDrawers = () => {
    setOpenDevDrawer(false);
    setOpenSiteDrawer(false);
  };

  //makes sure all of the Fields are filled out on the first form of the dialog otherwise the next button is disabled
  const formComplete = () => {
    if (siteName.length < 1) {
      return false;
    } else {
      return true;
    }
  };

  //save the new site to the database
  const saveSite = () => {
    let newSite = Site.create();
    newSite.settings.userId = user.id();
    newSite.settings.siteName = siteName;
    newSite.settings.objectKey = user.id();
    newSite.settings.longitude = newLong;
    newSite.settings.latitude = newLat;

    siteAPI
      .addSite(newSite.settings, as)
      .then(resp => {
        openSnack("Added new Site Location");
        loadSites();
      })
      .catch(err => {
        openSnack("Failed to add site");
      });
    setSiteDialog(false);
  };

  //load the sites from the database and set the markers for each of them
  const loadSites = useCallback(() => {
    if (loadingSites) return;
    setLoadingSites(true);
    let s: Map<string, Site> = new Map();
    let sm: Map<string, MarkerData> = new Map();
    let hMap: Map<string, pond.ObjectHeaterData[]> = new Map<string, pond.ObjectHeaterData[]>();
    let searchEntries: Result[] = [];
    siteAPI
      .listSitesPageData(50, 0, "asc", "key", undefined, as)
      .then(resp => {
        if(resp.data.sites){
        resp.data.sites.forEach(site => {
          if (!site) return;
          let newSite: Site = Site.any(site.site);
          s.set(newSite.key(), newSite);
          //if there is no long/lat on the site give it a default of saskatoon
          if (!newSite.longitude() || !newSite.latitude()) {
            newSite.settings.latitude = 52.1579;
            newSite.settings.longitude = -106.6702;
          }
          let siteMarker: MarkerData = {
            colour: newSite.settings.theme?.color ?? "blue",
            longitude: newSite.longitude(),
            latitude: newSite.latitude(),
            title: newSite.siteName(),
            markerIcon: <JobsiteIcon type="light" size={50 * 0.6} />,
            details: [newSite.siteName()],
            clickFunc: (e, i, isMobile) => {
              closeDrawers();
              setObjectKey(newSite.key());
              setOpenSiteDrawer(true);
              moveMap(newSite.latitude(), newSite.longitude(), zoomLevels.far, isMobile);
            },
            updateFunc: location => {
              if (newSite.settings) {
                updateSiteMarkers(newSite, location.longitude, location.latitude);
              }
            }
          };
          sm.set(newSite.key(), siteMarker);
          hMap.set(newSite.key(), site.heaterData);
          searchEntries.push({
            id: newSite.key(),
            center: [newSite.longitude(), newSite.latitude()],
            place_name: newSite.siteName(),
            place_type: ["site"]
          } as Result);
        });
        }
        setSiteMarkerData(sm);
        setSites(s);
        setLoadingSites(false);
        setHeaterMap(hMap);
        setSiteSearchEntries(searchEntries);
      })
      .catch(err => {
        openSnack("Failed to load sites");
      });
  }, [siteAPI, openSnack, as]); // eslint-disable-line react-hooks/exhaustive-deps

  const loadDevices = useCallback(() => {
    if (loadingDevices) return;
    setLoadingDevices(true);
    deviceAPI
      .list(500, 0, "asc", "name", undefined, undefined, undefined, undefined, undefined, true, undefined, undefined,undefined,undefined,undefined,as)
      .then(resp => {
        let devOps: Device[] = []; //only devices that are not mapped
        let deviceMap: Map<string, Device> = new Map<string, Device>(); // all devices
        let m = clone(devMarkerData);
        let searchEntries: Result[] = [];
        resp.data.comprehensiveDevices.forEach(cd => {
          if (cd.device?.settings?.deviceId) {
            let dev = Device.any(cd.device);
            deviceMap.set(dev.id().toString(), dev);
            let devLoc = dev.location();
            if (
              devLoc.longitude &&
              devLoc.longitude !== 0 &&
              devLoc.latitude &&
              devLoc.latitude !== 0
            ) {
              let devMarker: MarkerData = {
                colour: dev.settings.theme?.color || "blue",
                title: dev.name(),
                longitude: devLoc.longitude,
                latitude: devLoc.latitude,
                markerIcon: <NexusSTIcon size={50 * 0.6} type="light" />,
                details: [dev.name()],
                clickFunc: (e, i, isMobile) => {
                  closeDrawers();
                  clickDelay();
                  setObjectKey(dev.id().toString());
                  setOpenDevDrawer(true);
                  moveMap(devLoc.latitude, devLoc.longitude, zoomLevels.far, isMobile);
                },
                updateFunc: location => {
                  if (dev.settings) {
                    updateDeviceMarkers(dev, location.longitude, location.latitude);
                  }
                }
              };
              m.set(dev.id().toString(), devMarker);
              searchEntries.push({
                id: dev.id().toString(),
                center: [devLoc.longitude, devLoc.latitude],
                place_name: dev.name(),
                place_type: ["device"]
              } as Result);
            } else {
              devOps.push(dev);
            }
          }
        });
        setDevices(deviceMap);
        setDevOps(devOps);
        setLoadingDevices(false);
        setDevMarkerData(m);
        setDeviceSearchEntries(searchEntries);
      })
      .catch(err => {
        openSnack("Failed to Load Devices");
      });
  }, [deviceAPI, openSnack, as]); // eslint-disable-line react-hooks/exhaustive-deps

  const loadGroups = useCallback(() => {
    groupAPI
      .listGroups(50, 0)
      .then(resp => {
        let gMap: Map<string, Group> = new Map();
        let groupMarkers: Map<string, MarkerData> = new Map();
        let groupOps: Group[] = [];
        let searchEntries: Result[] = [];
        resp.data.groups.forEach((g: pond.Group) => {
          let group = Group.any(g);
          gMap.set(group.id().toString(), group);
          if (
            group.settings.longitude &&
            group.settings.longitude !== 0 &&
            group.settings.latitude &&
            group.settings.latitude !== 0
          ) {
            let marker: MarkerData = {
              colour: "blue",
              longitude: group.settings.longitude,
              latitude: group.settings.latitude,
              title: group.name(),
              markerIcon: <DeviceGroupIcon type="light" size={50 * 0.6} />,
              details: [group.name()],
              clickFunc: (e, i, isMobile) => {
                closeDrawers();
                clickDelay();
                setObjectKey(group.id().toString());
                setOpenGroupDrawer(true);
                moveMap(
                  group.settings.latitude,
                  group.settings.longitude,
                  zoomLevels.far,
                  isMobile
                );
              },
              updateFunc: location => {
                if (group) {
                  if (group.settings) {
                    let dataClone = clone(groupMarkerData);
                    updateGroupMarkers(group, location.longitude, location.latitude);
                    marker.longitude = location.longitude;
                    marker.latitude = location.latitude;
                    dataClone.set(group.id().toString(), marker);
                    setGroupMarkerData(dataClone);
                  }
                }
              }
            };
            groupMarkers.set(group.id().toString(), marker);
            searchEntries.push({
              id: group.id().toString(),
              center: [group.settings.longitude, group.settings.latitude],
              place_name: group.name(),
              place_type: ["group"]
            } as Result);
          } else {
            groupOps.push(group);
          }
        });
        setGroups(gMap);
        setGroupMarkerData(groupMarkers);
        setGroupOps(groupOps);
        setGroupSearchEntries(searchEntries);
      })
      .catch(err => {
        //openSnack("Failed to load Groups"); //backend function errors when there are no groups
      });
  }, [groupAPI]); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    loadSites();
    loadDevices();
    loadGroups();
  }, [loadSites, loadDevices, loadGroups, as]);

  //assemble the different objects markerdata into a single array to pass to the map
  useEffect(() => {
    let dArray = Array.from(devMarkerData.values());
    let sArray = Array.from(siteMarkerData.values());
    let gArray = Array.from(groupMarkerData.values());
    setMarkers(dArray.concat(sArray, gArray));
  }, [devMarkerData, siteMarkerData, groupMarkerData]);

  //assemble the different object search entries into one array to pass to the map
  useEffect(() => {
    setCustomGeoSearchEntries(siteSearchEntries.concat(deviceSearchEntries, groupSearchEntries));
  }, [deviceSearchEntries, siteSearchEntries, groupSearchEntries]);

  //the entry fields for the first part of the stepper
  const siteDetailsForm = () => {
    return (
      <Box>
        <TextField
          value={siteName}
          color="primary"
          autoFocus
          margin="dense"
          id="name"
          label="Site Name"
          type="text"
          fullWidth
          onChange={e => setSiteName(e.target.value)}></TextField>
      </Box>
    );
  };

  //dialog the renders the forms for creating a new site
  const dialogForSite = () => {
    return (
      <ResponsiveDialog
        fullWidth
        fullScreen={false}
        open={SiteDialog}
        onClose={() => {
          setSiteDialog(false);
        }}>
        <DialogTitle>Enter Name of Site</DialogTitle>
        <DialogContent>{siteDetailsForm()}</DialogContent>
        <DialogActions>
          <Button
            onClick={() => {
              setSiteDialog(false);
            }}
            color="primary">
            Cancel
          </Button>
          <Button onClick={saveSite} color="primary" disabled={!formComplete()}>
            Add Site
          </Button>
        </DialogActions>
      </ResponsiveDialog>
    );
  };

  const markerDialog = () => {
    return (
      <ResponsiveDialog
        open={openMarkerDialog}
        onClose={() => {
          setOpenMarkerDialog(false);
        }}>
        <DialogTitle>Place Marker</DialogTitle>
        <DialogContent>
          <Box width={300}>
            {markerType === pond.ObjectType.OBJECT_TYPE_DEVICE && (
              <Autocomplete
                id="autoYardList"
                options={devOps}
                getOptionLabel={(option: Device) => option.name()}
                fullWidth
                onChange={(e, val) => {
                  val && setSelectKey(val.id().toString());
                }}
                renderInput={params => <TextField {...params} label="Device" />}
              />
            )}
            {markerType === pond.ObjectType.OBJECT_TYPE_GROUP && (
              <Autocomplete
                id="autoYardList"
                options={groupOps}
                getOptionLabel={(option: Group) => option.name()}
                fullWidth
                onChange={(e, val) => {
                  val && setSelectKey(val.id().toString());
                }}
                renderInput={params => <TextField {...params} label="Group" />}
              />
            )}
          </Box>
        </DialogContent>
        <DialogActions>
          <Box>
            <Button onClick={closeNewMarkerDialog}>Cancel</Button>
            <Button onClick={setMarker}>Plot New Marker</Button>
          </Box>
        </DialogActions>
      </ResponsiveDialog>
    );
  };

  const setMarker = () => {
    if (markerType === pond.ObjectType.OBJECT_TYPE_DEVICE) {
      let dev = devices.get(selectKey);
      if (dev) {
        let key = dev.id().toString();
        let location = dev.location();
        let newDevMarker: MarkerData = {
          colour: dev.settings.theme?.color || "blue",
          title: dev.name(),
          longitude: newLong,
          latitude: newLat,
          markerIcon: <NexusSTIcon size={50 * 0.6} type="light" />,
          details: [dev.name()],
          clickFunc: (e, i, isMobile) => {
            closeDrawers();
            clickDelay();
            setObjectKey(key);
            setOpenDevDrawer(true);
            moveMap(location.latitude, location.longitude, zoomLevels.far, isMobile);
          },
          updateFunc: location => {
            if (dev) {
              if (dev.settings) {
                let dataClone = clone(devMarkerData);
                updateDeviceMarkers(dev, location.longitude, location.latitude);
                newDevMarker.longitude = location.longitude;
                newDevMarker.latitude = location.latitude;
                dataClone.set(key, newDevMarker);
                setDevMarkerData(dataClone);
              }
            }
          }
        };
        let devMarkers = clone(devMarkerData);
        devMarkers.set(key, newDevMarker);
        setDevMarkerData(devMarkers);
        updateDeviceMarkers(dev, newLong, newLat);
      }
    }
    if (markerType === pond.ObjectType.OBJECT_TYPE_GROUP) {
      let group = groups.get(selectKey);
      if (group) {
        let key = group.id().toString();
        let long = group.settings.longitude;
        let lat = group.settings.latitude;
        let newMarker: MarkerData = {
          colour: "blue",
          title: group.name(),
          longitude: newLong,
          latitude: newLat,
          markerIcon: <DeviceGroupIcon type="light" size={50 * 0.6} />,
          details: [group.name()],
          clickFunc: (e, i, isMobile) => {
            closeDrawers();
            clickDelay();
            setObjectKey(key);
            setOpenGroupDrawer(true);
            moveMap(lat, long, zoomLevels.far, isMobile);
          },
          updateFunc: location => {
            if (group) {
              if (group.settings) {
                let dataClone = clone(groupMarkerData);
                updateGroupMarkers(group, location.longitude, location.latitude);
                newMarker.longitude = location.longitude;
                newMarker.latitude = location.latitude;
                dataClone.set(key, newMarker);
                setGroupMarkerData(dataClone);
              }
            }
          }
        };
        let groupMarkers = clone(groupMarkerData);
        groupMarkers.set(key, newMarker);
        setGroupMarkerData(groupMarkers);
        updateGroupMarkers(group, newLong, newLat);
      }
    }
    closeNewMarkerDialog();
  };

  const closeNewMarkerDialog = () => {
    if (new Date().valueOf() > endTime) {
      setOpenMarkerDialog(false);
      setSelectKey("");
    }
  };

  const updateDeviceMarkers = (device: Device, long: number, lat: number) => {
    device.settings.longitude = long;
    device.settings.latitude = lat;
    deviceAPI
      .update(device.id(), device.settings, as)
      .then(resp => {
        openSnack("Device location Updated");
        if (devOps.includes(device)) {
          devOps.splice(devOps.indexOf(device), 1);
        }
      })
      .catch(err => {
        openSnack("Could not update Device Location");
      });
  };

  const updateGroupMarkers = (group: Group, long: number, lat: number) => {
    group.settings.longitude = long;
    group.settings.latitude = lat;
    groupAPI
      .updateGroup(group.id(), group.settings)
      .then(resp => {
        openSnack("Group location Updated");
        if (groupOps.includes(group)) {
          groupOps.splice(groupOps.indexOf(group), 1);
        }
      })
      .catch(err => {
        openSnack("Could not update Group Location");
      });
  };

  const updateSiteMarkers = (site: Site, long: number, lat: number) => {
    site.settings.longitude = long;
    site.settings.latitude = lat;
    siteAPI
      .updateSite(site.key(), site.settings, undefined, as)
      .then(resp => {
        openSnack("Site location updated");
      })
      .catch(err => {
        openSnack("Could not update site location");
      });
  };

  const moveMap = (
    lat: number,
    long: number,
    zoom: number,
    mobileOffset?: boolean,
    center?: boolean
  ) => {
    let xOff = !mobileOffset ? -250 : undefined;
    let yOff = mobileOffset ? -150 : undefined;

    setCurrentView({
      latitude: lat,
      longitude: long,
      zoom: zoom,
      transitionDuration: transDuration,
      xOffset: center ? 0 : xOff,
      yOffset: center ? 0 : yOff
    });
  };

  return (
    <Box style={{ height: "100%" }}>
      <MapBase
        placingMarker={markerType !== pond.ObjectType.OBJECT_TYPE_UNKNOWN}
        displayMarkerTitles={displayMarkerTitle}
        customSearchEntries={customGeoSearchEntries}
        geocoderResultFunction={result => {
          //open drawer
          setObjectKey(result.id?.toString() ?? "");
          if (result.place_type.includes("site")) {
            setOpenSiteDrawer(true);
          } else if (result.place_type.includes("device")) {
            setOpenDevDrawer(true);
          } else if (result.place_type.includes("group")) {
            setOpenGroupDrawer(true);
          }
        }}
        geocoderTransitionFunction={result => {
          //override the geocoders transition with our own
          let long = result.center[0];
          let lat = result.center[1];
          if (long && lat) {
            let centered = true;
            let zoom = zoomLevels.far;
            if (
              result.place_type.includes("site") ||
              result.place_type.includes("device") ||
              result.place_type.includes("group")
            ) {
              centered = false;
            }
            moveMap(lat, long, zoom, isMobile, centered);
          }
        }}
        mapTools={
          <ConstructionMapTools
            toggleMarkerType={setMarkerType}
            toggleMarkerDisplay={setDisplayMarkerTitle}
          />
        }
        mapClick={e => {
          if (markerType !== pond.ObjectType.OBJECT_TYPE_UNKNOWN) {
            setNewLong(e.lngLat.lng);
            setNewLat(e.lngLat.lat);
            if (markerType === pond.ObjectType.OBJECT_TYPE_SITE) {
              setSiteDialog(true);
            } else {
              setOpenMarkerDialog(true);
            }
          }
        }}
        currentView={currentView}
        markerData={markers}
      />
      {dialogForSite()}
      {markerDialog()}
      {objectKey !== "" && (
        <React.Fragment>
          <DeviceDrawer
            open={openDevDrawer}
            onClose={() => {
              if (new Date().valueOf() > endTime) {
                setOpenDevDrawer(false);
                setObjectKey("");
              }
            }}
            selectedDevice={objectKey}
            devices={devices}
            removeMarker={key => {
              if (new Date().valueOf() > endTime) {
                //use the key to remove it from the map of yard markers which should cause the markerData array to update
                let newMarkers = clone(devMarkerData);
                newMarkers.delete(key);
                setDevMarkerData(newMarkers);
                //adds it back to the options
                let dev = devices.get(key);
                if (dev) {
                  devOps.push(dev);
                }
              }
            }}
            updateMarker={(key, objectSettings) => {
              if (new Date().valueOf() > endTime) {
                let newMarkers = clone(devMarkerData);
                let toupdate = newMarkers.get(key);
                if (toupdate) {
                  if (objectSettings.theme) {
                    toupdate.colour = objectSettings.theme.color;
                  }
                }
                setDevMarkerData(newMarkers);
              }
            }}
            moveMap={(long, lat) => {
              moveMap(lat, long, zoomLevels.far, isMobile);
            }}
          />
          <SiteDrawer
            open={openSiteDrawer}
            sites={sites}
            heaters={heaterMap}
            selectedSite={objectKey}
            onClose={() => {
              if (new Date().valueOf() > endTime) {
                setOpenSiteDrawer(false);
                setObjectKey("");
              }
            }}
            moveMap={(long, lat) => {
              moveMap(lat, long, zoomLevels.far, isMobile);
            }}
            removeMarker={key => {
              if (new Date().valueOf() > endTime) {
                //use the key to remove it from the map of yard markers which should cause the markerData array to update
                let newMarkers = clone(siteMarkerData);
                newMarkers.delete(key);
                setSiteMarkerData(newMarkers);
              }
            }}
            updateMarker={(key, objectSettings) => {
              if (new Date().valueOf() > endTime) {
                let newMarkers = clone(siteMarkerData);
                let toupdate = newMarkers.get(key);
                if (toupdate) {
                  if (objectSettings.theme) {
                    toupdate.colour = objectSettings.theme.color;
                  }
                }
                setSiteMarkerData(newMarkers);
              }
            }}
            reLoadSites={() => {
              loadSites();
            }}
          />
          <GroupDrawer
            open={openGroupDrawer}
            groups={groups}
            moveMap={(long, lat) => {
              moveMap(lat, long, zoomLevels.far, isMobile);
            }}
            onClose={() => {
              if (new Date().valueOf() > endTime) {
                setOpenGroupDrawer(false);
                setObjectKey("");
              }
            }}
            removeMarker={key => {
              if (new Date().valueOf() > endTime) {
                //use the key to remove it from the map of yard markers which should cause the markerData array to update
                let newMarkers = clone(groupMarkerData);
                newMarkers.delete(key);
                setGroupMarkerData(newMarkers);
                //adds it back to the options
                let group = groups.get(key);
                if (group) {
                  groupOps.push(group);
                }
              }
            }}
            selectedGroup={objectKey}
          />
        </React.Fragment>
      )}
    </Box>
  );
}
