import {
  Autocomplete,
  Avatar,
  Box,
  Button,
  CircularProgress,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  Divider,
  Grid2 as Grid,
  IconButton,
  List,
  ListItem,
  ListItemAvatar,
  ListItemButton,
  ListItemSecondaryAction,
  ListItemText,
  ListSubheader,
  Tab,
  Tabs,
  TextField,
  Theme,
  Tooltip,
  Typography,
  useTheme
} from "@mui/material";
import { Component, Device } from "models";
import { pond } from "protobuf-ts/pond";
import React, { useCallback, useEffect, useState } from "react";
import { useComponentAPI, useDeviceAPI, useSnackbar } from "hooks";
import { useBinAPI, useGlobalState } from "providers";
import { GetComponentIcon } from "pbHelpers/ComponentType";
import { CheckBox as CheckBoxIcon, CheckBoxOutlineBlank, Remove } from "@mui/icons-material";
import ResponsiveDialog from "common/ResponsiveDialog";
import { quack } from "protobuf-ts/quack";
import { GetProductDefaults } from "products/DeviceProduct";
import DeviceWizard from "device/DeviceWizard";
import { makeStyles } from "@mui/styles";

const useStyles = makeStyles((theme: Theme) => {
  return ({
    stepper: {
      padding: theme.spacing(0.5)
    },
    secondaryColor: {
      color: theme.palette.secondary.main
    },
    textSecondaryColor: {
      color: theme.palette.text.secondary
    },
    bottomSpacing: {
      marginBottom: theme.spacing(1)
    },
    tabs: {
      width: "100%",
      boxSizing: "border-box",
      flexShrink: 1
    },
    tabSmall: {
      width: "100%",
      boxSizing: "border-box",
      flexShrink: 1,
      minWidth: "48px",
      marginTop: 0,
      paddingTop: 0
    }
  })
});

interface Props {
  components?: Map<string, Component>;
  setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
  bin: string;
  binGrain: pond.Grain;
  updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
}

interface Option {
  label: string;
  id: number;
  icon?: string;
}

export default function BinComponents(props: Props) {
  const { components, bin, setComponents, updateBinStatus, binGrain } = props;
  const [{as}] = useGlobalState();
  const classes = useStyles();
  const binAPI = useBinAPI();
  const theme = useTheme();
  const deviceAPI = useDeviceAPI();
  const componentAPI = useComponentAPI();
  const snackbar = useSnackbar();
  const [devicesLoading, setDevicesLoading] = useState<boolean>(false);
  const [componentsLoading, setComponentsLoading] = useState<boolean>(false);
  const [deviceOptions, setDeviceOptions] = useState<Option[]>([]);
  const [devMap, setDevMap] = useState<Map<number, Device>>(new Map());
  const [selectedDevice, setSelectedDevice] = useState<number>(-1);
  const [deviceComponents] = useState<Map<number, Component[]>>(new Map<number, Component[]>());
  const [activeStep, setActiveStep] = useState(0);
  const [componentToRemove, setComponentToRemove] = useState<Component | undefined>(undefined);
  const [removeAllDialog, setRemoveAllDialog] = useState(false);
  const [wizardOpen, setWizardOpen] = useState(false);
  const [setupAllowed, setSetupAllowed] = useState(false);

  const loadDevices = useCallback(() => {
    let mounted = true;
    setDevicesLoading(true);
    deviceAPI
      .list(100000, 0, "asc", "name",undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,as)
      .then(response => {
        if (mounted) {
          let data = response.data.devices ? response.data.devices : [];
          let newDevMap: Map<number, Device> = new Map();
          let devices: Option[] = data.map(d => {
            let device = Device.create(pond.Device.fromObject(d));
            newDevMap.set(device.id(), device);
            return { id: device.id(), label: device.name() } as Option;
          });
          setDevMap(newDevMap);
          setDeviceOptions(devices);
        }
      })
      .catch(() => {
        if (mounted) {
          setDeviceOptions([]);
        }
      })
      .finally(() => {
        if (mounted) {
          setDevicesLoading(false);
        }
      });
    return () => {
      mounted = false;
    };
  }, [deviceAPI, as]);

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

  const loadComponents = useCallback(
    (forceLoad?: boolean) => {
      if (selectedDevice < 1) return;
      let comps = deviceComponents.get(selectedDevice);
      if (comps === undefined || forceLoad) {
        comps = [];
        setComponentsLoading(true);
        componentAPI
          .list(selectedDevice, false, [selectedDevice.toString()], ["device"], true, as)
          .then(resp => {
            let d = pond.ListComponentsResponse.fromObject(resp.data);
            d.components.forEach(comp => {
              // Don't show modems
              if (comp.settings?.type === quack.ComponentType.COMPONENT_TYPE_MODEM) return;
              let c = Component.create(comp);
              if (c.permissions.includes(pond.Permission.PERMISSION_WRITE)) {
                setSetupAllowed(true);
              }
              comps!.push(c);
            });
            if (comps) deviceComponents.set(selectedDevice, comps);
          })
          .catch(err => {
            snackbar.error(err);
          })
          .finally(() => {
            setComponentsLoading(false);
          });
      }
    },
    [selectedDevice, componentAPI, deviceComponents, snackbar, as]
  );

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

  // useEffect(() => {
  //   if (selectedDevice < 1) return;
  //   let comps = deviceComponents.get(selectedDevice);
  //   if (comps === undefined) {
  //     comps = [];
  //     setComponentsLoading(true);
  //     componentAPI
  //       .list(selectedDevice, false, [selectedDevice.toString()], ["device"], true)
  //       .then(resp => {
  //         let d = pond.ListComponentsResponse.fromObject(resp.data);
  //         d.components.forEach(comp => {
  //           // Don't show modems
  //           if (comp.settings?.type === quack.ComponentType.COMPONENT_TYPE_MODEM) return;
  //           let c = Component.create(comp)
  //           if(c.permissions.includes(pond.Permission.PERMISSION_WRITE)){
  //             setSetupAllowed(true)
  //           }
  //           comps!.push(c);
  //         });
  //         if (comps) deviceComponents.set(selectedDevice, comps);
  //       })
  //       .catch(err => {
  //         snackbar.error(err);
  //       })
  //       .finally(() => {
  //         setComponentsLoading(false);
  //       });
  //   }
  // }, [selectedDevice, componentAPI, deviceComponents, snackbar]);

  const removeComponent = (component: string) => {
    binAPI.removeComponent(bin, component, as).then(() => {
      if (components && setComponents) {
        if (components.delete(component)) {
          let newComponents = new Map(components);
          setComponents(newComponents);
        }
      }
      snackbar.info("Component removed from bin");
    });
    setComponentToRemove(undefined);
  };

  const removeComponentConfirmation = () => {
    return (
      <ResponsiveDialog
        fullScreen={false}
        open={componentToRemove !== undefined}
        onClose={() => setComponentToRemove(undefined)}>
        <DialogTitle>Remove {componentToRemove?.name()}?</DialogTitle>
        <DialogContent>
          <DialogContentText>
            This will remove {componentToRemove?.name()} from this bin. If you don't have direct
            access to this component or the device it is attached to, you will not be able to add it
            back.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setComponentToRemove(undefined)} color="primary">
            Cancel
          </Button>
          <Button onClick={() => removeComponent(componentToRemove!.key())} color="primary">
            Remove
          </Button>
        </DialogActions>
      </ResponsiveDialog>
    );
  };

  const removeAllConfirmation = () => {
    return (
      <ResponsiveDialog
        fullScreen={false}
        open={removeAllDialog}
        onClose={() => setRemoveAllDialog(false)}>
        <DialogTitle>Remove All Components?</DialogTitle>
        <DialogContent>
          <DialogContentText>
            This will remove All attached components from this bin. If you don't have direct access
            to these components or the devices they are attached to, you will not be able to add
            them back.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setRemoveAllDialog(false)} color="primary">
            Cancel
          </Button>
          <Button onClick={() => removeAll()} color="primary">
            Remove
          </Button>
        </DialogActions>
      </ResponsiveDialog>
    );
  };

  const determinePreset = (comp: Component, devProduct?: pond.DeviceProduct) => {
    let preset = pond.BinComponent.BIN_COMPONENT_UNKNOWN;
    if (devProduct) {
      let defaults = GetProductDefaults(devProduct);
      let plenumAddresses: string[] | undefined = defaults?.get("plenum");
      let headspaceAddresses: string[] | undefined = defaults?.get("headspace");
      let pressureAddresses: string[] | undefined = defaults?.get("pressure");
      let heatersFansAddresses: string[] | undefined = defaults?.get("heatersFans");
      if (plenumAddresses && plenumAddresses.includes(comp.locationString())) {
        preset = pond.BinComponent.BIN_COMPONENT_PLENUM;
      } else if (pressureAddresses && pressureAddresses.includes(comp.locationString())) {
        preset = pond.BinComponent.BIN_COMPONENT_PRESSURE;
      } else if (headspaceAddresses && headspaceAddresses.includes(comp.locationString())) {
        preset = pond.BinComponent.BIN_COMPONENT_HEADSPACE;
      } else if (heatersFansAddresses && heatersFansAddresses.includes(comp.locationString())) {
        switch (comp.subType()) {
          case quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER:
            preset = pond.BinComponent.BIN_COMPONENT_HEATER;
            break;
          case quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN:
          case quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN:
            preset = pond.BinComponent.BIN_COMPONENT_FAN;
            break;
        }
      } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
        //grain cables should be treated as cables by default no matter what the address is
        preset = pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE;
      } // else if ("is a pressure cable"){}
    }
    return preset;
  };

  const toggleComponent = (device: number, component: Component, checked: boolean) => {
    if (checked) {
      let devObj = devMap.get(device);
      let preset = determinePreset(component, devObj?.settings.product);
      let preferences = pond.BinComponentPreferences.create({
        type: preset,
        node: component.settings.grainFilledTo ?? 0
      });
      binAPI.addComponent(bin, device, component.key(), preferences, as).then(resp => {
        if (components && setComponents) {
          if (components.set(component.key(), component)) {
            let newComponents = new Map(components);
            setComponents(newComponents);
          }
        }
        //if a grain cable was added to the bin update the components grain type to match the bin
        if (preferences.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) {
          let settings = component.settings;
          settings.grainType = binGrain;
          settings.defaultMutations = [pond.Mutator.MUTATOR_EMC];
          componentAPI
            .update(device, settings, undefined, undefined, as)
            .then(resp => {
              snackbar.info("Component added to bin, and updated cables grain type");
            })
            .catch(err => {
              snackbar.info("Component added to bin, but failed to update grain type on cable");
            });
        } else {
          snackbar.info("Component added to bin");
        }
      });
    } else {
      binAPI.removeComponent(bin, component.key(), as).then(resp => {
        if (components && setComponents) {
          if (components.delete(component.key())) {
            let newComponents = new Map(components);
            setComponents(newComponents);
          }
        }
        snackbar.info("Component removed from bin");
      });
    }
  };

  const removeAll = () => {
    binAPI.removeAllComponents(bin, as).then(resp => {
      if (setComponents) {
        setComponents(new Map());
      }
      if (components && updateBinStatus) {
        updateBinStatus(Array.from(components.keys()), true);
      }
      snackbar.info("All components removed from bin");
      setRemoveAllDialog(false);
    });
  };

  const deviceComponentsDisplay = (device: number) => {
    if (deviceComponents.get(device) && deviceComponents.get(device)!.length < 1) {
      return (
        <Typography
          variant="subtitle2"
          style={{
            margin: theme.spacing(2),
            display: "flex",
            justifyContent: "center"
          }}>
          No Sensors
        </Typography>
      );
    }
    return (
      <React.Fragment>
        {deviceComponents.get(device)?.map((comp, index) => {
          let cIcon = GetComponentIcon(
            comp.settings.type,
            comp.settings.subtype,
            theme.palette.mode
          );
          let share = comp.permissions.includes(pond.Permission.PERMISSION_SHARE);

          return (
            <React.Fragment key={index}>
              <Tooltip
                title={
                  !share
                    ? "User or team does not have share permissions on this device"
                    : "Share component with bin"
                }
                key={index}>
                <div>
                  <ListItemButton
                    disabled={!share}
                    onClick={() =>
                      toggleComponent(device, comp, components?.get(comp.key()) === undefined)
                    }>
                    {cIcon && (
                      <ListItemAvatar>
                        <Avatar
                          variant="square"
                          src={cIcon}
                          alt={comp.name()}
                          style={{ width: theme.spacing(3), height: theme.spacing(3) }}
                        />
                      </ListItemAvatar>
                    )}
                    <ListItemText inset={cIcon === undefined}>
                      <Grid container direction="row" justifyContent="space-between">
                        <Grid >{comp.name() + " - " + comp.addressDescription(devMap.get(device)?.settings.product)}</Grid>
                        <Grid >
                          {components?.get(comp.key()) !== undefined ? (
                            <CheckBoxIcon />
                          ) : (
                            <CheckBoxOutlineBlank />
                          )}
                        </Grid>
                      </Grid>
                    </ListItemText>
                  </ListItemButton>
                </div>
              </Tooltip>
              <Divider />
            </React.Fragment>
          );
        })}
      </React.Fragment>
    );
  };

  const wizardDialog = () => {
    let device = devMap.get(selectedDevice);
    let components = deviceComponents.get(selectedDevice);
    if (device && components) {
      return (
        <ResponsiveDialog
          open={wizardOpen}
          onClose={() => {
            setWizardOpen(false);
          }}>
          <DeviceWizard
            device={device}
            components={components}
            refreshCallback={() => {
              loadComponents(true);
            }}
          />
        </ResponsiveDialog>
      );
    }
    return;
  };

  const componentsByDevice = () => {
    return (
      <React.Fragment>
        <Typography variant="subtitle2" align="center" color="primary" gutterBottom>
          Bin Sensors
        </Typography>
        <Box marginY={1}>
          <Autocomplete
            disablePortal
            options={deviceOptions}
            getOptionLabel={option => option.label || ""}
            onChange={(_, newValue) => {
              setSelectedDevice(newValue ? newValue.id : -1);
            }}
            renderInput={params => <TextField {...params} variant="outlined" label="Device" />}
            loading={devicesLoading}
          />
          {selectedDevice < 0 ? (
            <Typography
              variant="subtitle2"
              style={{
                margin: theme.spacing(3),
                display: "flex",
                justifyContent: "center"
              }}>
              Select a device
            </Typography>
          ) : componentsLoading ? (
            <CircularProgress />
          ) : (
            <React.Fragment>
              {setupAllowed && (
                <Button
                  variant="contained"
                  color="primary"
                  onClick={() => {
                    setWizardOpen(true);
                  }}
                  style={{ marginTop: 15 }}>
                  Device Setup
                </Button>
              )}
              <List
                disablePadding
                subheader={
                  deviceComponents?.get(selectedDevice) && (
                    <React.Fragment>
                      <ListSubheader component="div" disableGutters>
                        Choose Sensors
                      </ListSubheader>
                      <Divider />
                    </React.Fragment>
                  )
                }>
                {deviceComponentsDisplay(selectedDevice)}
              </List>
            </React.Fragment>
          )}
        </Box>
      </React.Fragment>
    );
  };

  const attachedComponents = () => {
    if (components === undefined) return;
    let comps = [...components.values()];
    return (
      <React.Fragment>
        <Button
          onClick={() => {
            setRemoveAllDialog(true);
          }}>
          Remove All Components
        </Button>
        <List>
          <ListSubheader component="div" disableGutters>
            This Bin's Sensors
          </ListSubheader>
          <Divider />
          {comps.length < 1 && (
            <Typography
              variant="subtitle2"
              style={{
                margin: theme.spacing(2),
                display: "flex",
                justifyContent: "center"
              }}>
              No Sensors
            </Typography>
          )}
          {comps.map((component, index) => {
            let cIcon = GetComponentIcon(
              component.settings.type,
              component.settings.subtype,
              theme.palette.mode
            );
            return (
              <React.Fragment key={index}>
                <ListItem>
                  <ListItemAvatar>
                    <Avatar
                      variant="square"
                      src={cIcon}
                      alt={component.name()}
                      style={{ width: theme.spacing(3), height: theme.spacing(3) }}
                    />
                  </ListItemAvatar>
                  <ListItemText inset={cIcon === undefined}>{component.name()}</ListItemText>
                  <ListItemSecondaryAction>
                    <IconButton onClick={() => setComponentToRemove(component)}>
                      <Remove />
                    </IconButton>
                  </ListItemSecondaryAction>
                </ListItem>
                <Divider />
              </React.Fragment>
            );
          })}
        </List>
      </React.Fragment>
    );
  };

  return (
    <React.Fragment>
      <Tabs
        value={activeStep}
        onChange={(_, value) => setActiveStep(value)}
        indicatorColor="primary"
        textColor="primary"
        variant="fullWidth"
        aria-label="bin tabs"
        classes={{ root: classes.tabSmall }}>
        <Tab label={"Your Devices"} className={classes.tabSmall} />
        <Tab label={"Attached"} className={classes.tabSmall} />
      </Tabs>
      <Box width="100%" marginTop={activeStep === 0 ? 2 : 1}>
        {activeStep === 0 ? componentsByDevice() : attachedComponents()}
      </Box>
      {removeComponentConfirmation()}
      {removeAllConfirmation()}
      {wizardDialog()}
    </React.Fragment>
  );
}
