import { useCallback, useEffect, useRef, useState } from "react";
import PageContainer from "./PageContainer";
import { DevicePreset } from "models/DevicePreset";
import { Controller } from "models/Controller";
import { useNavigate, useParams } from "react-router-dom";
import { useMobile, useSnackbar } from "hooks";
import { Component, Device, Bin as IBin, Interaction } from "models";
import { useBinAPI, useGlobalState } from "providers";
import { pond } from "protobuf-ts/pond";
import { Plenum } from "models/Plenum";
import { Ambient } from "models/Ambient";
import { GrainCable } from "models/GrainCable";
import { Pressure } from "models/Pressure";
import { CO2 } from "models/CO2";
import moment from "moment";
import { quack } from "protobuf-ts/quack";
import { Box, Drawer, Grid2 as Grid, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tab, Tabs, Theme, Tooltip } from "@mui/material";
import { makeStyles } from "@mui/styles";
import NotesIcon from "@mui/icons-material/Notes";
import TasksIcon from "products/Construction/TasksIcon";
import BindaptIcon from "products/Bindapt/BindaptIcon";
import FieldsIcon from "products/AgIcons/FieldsIcon";
import BinActions from "bin/BinActions";
import BinSummary from "bin/binSummary/BinSummary";
import { Close } from "@mui/icons-material";
import React from "react";
import BinHistory from "bin/BinHistory";
import Chat from "chat/Chat";
import TaskViewer from "tasks/TaskViewer";

interface TabPanelProps {
  children?: React.ReactNode;
  index: any;
  value: any;
}

function TabPanelMine(props: TabPanelProps) {
  const { children, value, index, ...other } = props;

  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      aria-labelledby={`simple-tab-${index}`}
      {...other}>
      {value === index && <React.Fragment>{children}</React.Fragment>}
    </div>
  );
}

const useStyles = makeStyles((theme: Theme) => {
    const themeType = theme.palette.mode;
    return ({
        spacer: {
            width: "32px",
            height: "32px",
            padding: "auto",
            display: "flex",
            justifyContent: "center",
            alignItems: "center"
        },
        avatar: {
            color: themeType === "light" ? theme.palette.common.black : theme.palette.common.white,
            backgroundColor: "transparent",
            width: theme.spacing(5),
            height: theme.spacing(5),
            border: "1px solid",
            borderColor: theme.palette.divider
        },
        menuPaper: {
          borderRadius: "20px"
        },
        drawerPaperDesktop: {
          height: "100%",
          width: "30%"
        },
        drawerPaperMobile: {
          height: "50%",
          width: "100%"
        },
    })
})


export default function BinV2(){
    const warningThreshold = 6;
    const isMobile = useMobile();
    const binID = useParams<{ binID: string }>()?.binID ?? "";
    const binAPI = useBinAPI();
    const classes = useStyles()
    const navigate = useNavigate()
    const [{ user, as, showErrors }] = useGlobalState();
    const [binLoading, setBinLoading] = useState(false);
    const loadRef = useRef<boolean>(false);
    const [bin, setBin] = useState<IBin>(IBin.create());
    const [devices, setDevices] = useState<Device[]>([]);
    const [components, setComponents] = useState<Map<string, Component>>(new Map());
    const [interactions, setInteractions] = useState<Interaction[]>([]);
    const [permissions, setPermissions] = useState<pond.Permission[]>([]);
    const [preferences, setPreferences] = useState<Map<string, pond.BinComponentPreferences>>();
    const [plenums, setPlenums] = useState<Plenum[]>([]);
    const [ambients, setAmbients] = useState<Ambient[]>([]);
    const [grainCables, setGrainCables] = useState<GrainCable[]>([]);
    const [pressures, setPressures] = useState<Pressure[]>([]);
    const [headspaceCO2, setHeadspaceCO2] = useState<CO2[]>([]);
    const [heaters, setHeaters] = useState<Controller[]>([]);
    const {openSnack} = useSnackbar()
    const [fans, setFans] = useState<Controller[]>([]);
    const [compositionNameMap, setCompositionNameMap] = useState<Map<string, string>>(new Map());
    const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
    const [noteTab, setNoteTab] = useState(0)

  
    const [componentDevices, setComponentDevices] = useState<Map<string, number>>(
      new Map<string, number>()
    );
    const [interactionDevices, setInteractionDevices] = useState<Map<string, number>>(
      new Map<string, number>()
    );
    const [binPresets, setBinPresets] = useState<DevicePreset[]>([]);
    const [missedReadings, setMissedReadings] = useState(0);

    //Drawer states
    const [noteDrawerOpen, setNoteDrawerOpen] = useState(false)
    const [taskDrawerOpen, setTaskDrawerOpen] = useState(false)

    //DATA LOAD/UPDATE FUNCTIONS

    const load = useCallback(() => {
        if (loadRef.current || user.id() === "") return;
        setBinLoading(true);
        loadRef.current = true;
        //add the presets to the bin page data load
        binAPI
          .getBinPageData(binID, user.id(), showErrors, as)
          .then(resp => {
            if (resp.data.grainCompositionNames) {
              let tempMap: Map<string, string> = new Map();
              Object.keys(resp.data.grainCompositionNames).forEach(key => {
                tempMap.set(key, resp.data.grainCompositionNames[key]);
              });
              tempMap.set("correction", "Unknown Source");
              setCompositionNameMap(tempMap);
            }
    
            let devs: Device[] = [];
            let p = new Map<string, pond.BinComponentPreferences>();
            Object.keys(resp.data.preferences).forEach(k => {
              let prefKey = k.split(":")[1] ? k.split(":")[1] : k;
              p.set(prefKey, pond.BinComponentPreferences.fromObject(resp.data.preferences[k]));
            });
            setPreferences(p);
            let r: pond.GetBinPageDataResponse = pond.GetBinPageDataResponse.fromObject(resp.data);
            setBin(IBin.any(resp.data.bin));
            let newComponentDevices = new Map<string, number>();
            let attachedDevIds: number[] = [];
            if (resp.data.componentDevices)
              Object.keys(resp.data.componentDevices).forEach(k => {
                newComponentDevices.set(k, resp.data.componentDevices[k]);
                //make a list of all of the ids for devices that components are currently attached
                if (!attachedDevIds.includes(resp.data.componentDevices[k])) {
                  attachedDevIds.push(resp.data.componentDevices[k]);
                }
              });

            setComponentDevices(newComponentDevices);
    
            let newInteractionDevices = new Map<string, number>();
            if (r.interactionDevices)
              Object.keys(r.interactionDevices).forEach(k => {
                newInteractionDevices.set(k, r.interactionDevices[k]);
              });
            setInteractionDevices(newInteractionDevices);
    
            if (resp.data.interactions) {
              setInteractions(resp.data.interactions.map((i: pond.Interaction) => Interaction.any(i)));
            } else {
              setInteractions([]);
            }
            //go through the devices and if the device has an attached component include it in the device list
            if (resp.data.devices && resp.data.devices[0]) {
              resp.data.devices.forEach((dev: any) => {
                let device = Device.create(dev);
                if (attachedDevIds.includes(device.id())) {
                  devs.push(device);
                }
              });
              setDevices(devs);
            }
            if (resp.data.components) {
              let compMap: Map<string, Component> = new Map();
              resp.data.components.forEach((comp: pond.Component) => {
                if (comp && comp.settings) {
                  let c = Component.any(comp);
                  compMap.set(comp.settings.key, c);
                }
              });
              setComponents(compMap);
            }
            if (resp.data.presets) {
              setBinPresets(
                resp.data.presets.map((preset: pond.DevicePreset) => DevicePreset.create(preset))
              );
            }
            setPermissions(r.permissions ? r.permissions : []);
            setBinLoading(false);
            loadRef.current = false;
          })
          .finally(() => {
            setBinLoading(false);
            loadRef.current = false;
          });
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, [binID, user.id(), binAPI, showErrors, as]);
    
      useEffect(() => {
        load();
      }, [load]);
    
      const setBinComponents = useCallback(() => {
        if (!preferences) return;
        var unassigned: Component[] = [];
        var plenums: Plenum[] = [];
        var ambients: Ambient[] = [];
        var grainCables: GrainCable[] = [];
        var fans: Controller[] = [];
        var heaters: Controller[] = [];
        var pressures: Pressure[] = [];
        var headspaceCo2: CO2[] = [];
        var mostMissed: number = 0;
        let now = moment();
    
        components.forEach(comp => {
          let pref = preferences.get(comp.key());
          if (pref) {
            if (pref.type) {
              if (pref.type === pond.BinComponent.BIN_COMPONENT_PLENUM)
                plenums.push(Plenum.create(comp));
              if (pref.type === pond.BinComponent.BIN_COMPONENT_AMBIENT)
                ambients.push(Ambient.create(comp));
              if (pref.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) {
                //check cable for missed measurements
                let cable = GrainCable.create(comp);
                let lastRead = moment(cable.lastReading);
                let elapsedMS = now.diff(lastRead);
                if (elapsedMS > cable.settings.measurementPeriodMs) {
                  let missedReadings = Math.floor(elapsedMS / cable.settings.measurementPeriodMs);
                  if (missedReadings > mostMissed) {
                    mostMissed = missedReadings;
                  }
                }
                grainCables.push(cable);
              }
              if (pref.type === pond.BinComponent.BIN_COMPONENT_HEATER)
                heaters.push(Controller.create(comp));
              if (pref.type === pond.BinComponent.BIN_COMPONENT_FAN) fans.push(Controller.create(comp));
              if (pref.type === pond.BinComponent.BIN_COMPONENT_PRESSURE)
                pressures.push(Pressure.create(comp));
              if (pref.type === pond.BinComponent.BIN_COMPONENT_HEADSPACE) {
                if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
                  //check if there are missed readings from the co2 and compare them to what we already had
                  let coComp = CO2.create(comp)
                  let lastRead = moment(coComp.lastReading);
                  let elapsedMS = now.diff(lastRead);
                  if (elapsedMS > coComp.settings.measurementPeriodMs) {
                    let missedReadings = Math.floor(elapsedMS / coComp.settings.measurementPeriodMs);
                    if (missedReadings > mostMissed) {
                      mostMissed = missedReadings;
                    }
                  }
                  headspaceCo2.push(coComp);
                }
              }
            } else {
              unassigned.push(comp);
            }
          }
        });
        setMissedReadings(mostMissed);
        setPlenums(plenums);
        setGrainCables(grainCables);
        setPressures(pressures);
        setAmbients(ambients);
        setHeaters(heaters);
        setFans(fans);
        setHeadspaceCO2(headspaceCo2);
      }, [components, preferences]);

      useEffect(() => {
        setBinComponents();
      }, [setBinComponents]);

      const updateStatus = (componentKeys: string[], removed?: boolean) => {
        componentKeys.forEach(compKey => {
          let comp = components.get(compKey);
          if (comp) {
            //determine what part of the status to update based on the component
            //for lidar
            if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) {
              //determine how to update the status based on if added or removed
              if (removed) {
                bin.status.distance = 0;
              } else {
                if (
                  comp.lastMeasurement[0] &&
                  comp.lastMeasurement[0].values[0] &&
                  comp.lastMeasurement[0].values[0].values[0]
                ) {
                  bin.status.distance = comp.lastMeasurement[0].values[0].values[0];
                }
              }
            }
          }
        });
        //update the bins status with the new values based on the components changed
        binAPI.updateBinStatus(bin.key(), bin.status, as);
      };

      //NAVIGATION FUNCTIONS
      const goToDevice = (dev: Device) => {
          navigate(window.location.pathname + "/devices/" + dev.id(), { replace: true });
      };

      const goToYard = (bin: IBin) => {
        navigate("/visualFarm", { state: {
            long: bin.getLocation()?.longitude,
            lat: bin.getLocation()?.latitude
          }
        });
      };


    /**
     * UI STUFF STARTS HERE
     */

    const deviceMenu = () => {
      return (
        <Menu
          id="groupMenu"
          anchorEl={anchorEl}
          open={Boolean(anchorEl)}
          onClose={() => setAnchorEl(null)}
          keepMounted
          classes={{ paper: classes.menuPaper }}
          disableAutoFocusItem>
          {devices.map(dev => (
            <MenuItem key={dev.id()} onClick={() => goToDevice(dev)}>
              <ListItemIcon>
                <BindaptIcon />
              </ListItemIcon>
              <ListItemText primary={dev.name()} />
            </MenuItem>
          ))}
        </Menu>
      );
    };

    /**
     * Header - consists of the actions and things that are always visible like
     * opening the note/history and tasks drawers, navigating to the device and map pages, opening binsensors and settings etc.
     * also has the bin name/model and activity and the dropdown for changing sections
     */
    const pageHeader = () => {
        return (
          <Box margin={2} marginBottom={0}>
            <Grid
              container
              direction="row"
              justifyContent="space-between"
              alignContent="center"
              alignItems="center">
              <Grid >
                <IconButton
                  onClick={() =>
                    setNoteDrawerOpen(true)
                  }
                  size="small"
                  style={{
                    marginRight: "0.5rem"
                  }}
                  className={classes.avatar}
                  component="span">
                  <div className={classes.spacer}>
                    <NotesIcon />
                  </div>
                </IconButton>
                <IconButton
                  onClick={() =>
                    setTaskDrawerOpen(true)
                  }
                  size="small"
                  style={{
                    marginRight: "0.5rem"
                  }}
                  className={classes.avatar}
                  component="span">
                  <div className={classes.spacer}>
                    <TasksIcon />
                  </div>
                </IconButton>
                {devices.length > 1 ? (
                  <Tooltip key="devices" title="devices">
                    <IconButton
                      key="devButton"
                      onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
                        setAnchorEl(event.currentTarget)
                      }
                      size="small"
                      style={{
                        marginRight: "0.5rem"
                      }}
                      className={classes.avatar}
                      component="span">
                      <BindaptIcon />
                    </IconButton>
                  </Tooltip>
                ) : (
                  devices.map(dev => (
                    <Tooltip key={dev.id()} title={dev.name()}>
                      <IconButton
                        key={dev.id()}
                        onClick={() => goToDevice(dev)}
                        size="small"
                        style={{
                          marginRight: "0.5rem"
                        }}
                        className={classes.avatar}
                        component="span">
                        <div className={classes.spacer}>
                          <BindaptIcon />
                        </div>
                      </IconButton>
                    </Tooltip>
                  ))
                )}
                {bin.binMapped() && (
                  <Tooltip key={"goToMap"} title={"Go to Yard"}>
                    <IconButton
                      onClick={() => {
                        goToYard(bin);
                      }}
                      size="small"
                      style={{
                        marginRight: "0.5rem"
                      }}
                      className={classes.avatar}
                      component="span">
                      <FieldsIcon />
                    </IconButton>
                  </Tooltip>
                )}
              </Grid>
              <Grid>
                <BinActions
                  bin={bin}
                  permissions={permissions}
                  refreshCallback={() => {
                    load();
                  }}
                  userID={user.id()}
                  components={components}
                  setComponents={setComponents}
                  componentDevices={componentDevices}
                  setComponentDevices={setComponentDevices}
                  updateBinStatus={updateStatus}
                />
              </Grid>
            </Grid>
          </Box>
        )
    }

    const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => {
      setNoteTab(newValue);
    };


    //DRAWERS
    const noteDrawer = () => {
        return (
          <Drawer
          open={noteDrawerOpen}
          anchor={isMobile ? "bottom" : "right"}
          classes={{ paper: isMobile ? classes.drawerPaperMobile : classes.drawerPaperDesktop }}
          onClose={() => setNoteDrawerOpen(false)}>
          <IconButton style={{ width: 50 }} onClick={() => setNoteDrawerOpen(false)}>
            <Close />
          </IconButton>
          <Tabs
            value={noteTab}
            indicatorColor="primary"
            textColor="primary"
            onChange={handleChange}
            aria-label="disabled tabs example"
            // centered={!(isMobile || displayMobile)}
            style={{
              marginLeft: "auto"
              //marginTop: isMobile || displayMobile || drawer ? "-3rem" : "0.25rem"
            }}>
            <Tab label="Notes" />
            <Tab label="History" />
          </Tabs>
          <TabPanelMine value={noteTab} index={0}>
            {/* <Box height={isMobile || displayMobile ? "80vh" : "90vh"} padding={2}> */}
              <Chat parent={binID} parentType={"bin"} type={pond.NoteType.NOTE_TYPE_BIN} />
            {/* </Box> */}
          </TabPanelMine>
          <TabPanelMine value={noteTab} index={1}>
            <BinHistory drawer binID={binID} />
          </TabPanelMine>
        </Drawer>
        )
    }

    const taskDrawer = () => {
        return (
          <Drawer
          style={{ zIndex: 1099 }}
          open={taskDrawerOpen}
          anchor={isMobile ? "bottom" : "right"}
          classes={{ paper: isMobile ? classes.drawerPaperMobile : classes.drawerPaperDesktop }}
          onClose={() => setTaskDrawerOpen(false)}>
          <Box>
            <IconButton style={{ width: 50 }} onClick={() => setTaskDrawerOpen(false)}>
              <Close />
            </IconButton>
            <TaskViewer drawerView keys={[binID]} types={["bin"]} overlayButton />
          </Box>
        </Drawer>
        )
    }

    return (
        <PageContainer>
            {deviceMenu()}
            {pageHeader()}
            <BinSummary 
              bin={bin} 
              devices={devices} 
              fans={fans} 
              heaters={heaters}
              cables={grainCables}
              plenums={plenums}
              permissions={permissions} 
              componentDevices={componentDevices} 
              componentMap={components}
              binPrefs={preferences}
              setPreferences={setPreferences}
              updateBinCallback={(bin) => {
                setBin(bin)
                binAPI.updateBin(bin.key(), bin.settings).then(resp => {
                  openSnack("Bin has Been Updated")
                })
              }}
              setBin={setBin}
            />
            {/* render drawers */}
            {noteDrawer()}
            {taskDrawer()}
        </PageContainer>
    )
}