import { Autocomplete, Box, Button, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, Stepper, Step, StepLabel, InputAdornment, Typography, useTheme } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import { useComponentAPI, useInteractionsAPI } from "hooks";
import { Component, Device, Interaction } from "models";
import { Ambient } from "models/Ambient";
import { Controller } from "models/Controller";
import { DevicePreset } from "models/DevicePreset";
import { GrainCable } from "models/GrainCable";
import { Plenum } from "models/Plenum";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useGlobalState } from "providers";
import React from "react";
import { useEffect, useState } from "react";
import ConditioningSelector from "./conditioningSelector";
import CableTopNodeSummary from "bin/CableTopNodeSummary";
import { ComponentSet } from "./pickComponentSet";
import { sameComponentID } from "pbHelpers/Component";
import moment from "moment";
import { lowerCase } from "lodash";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { GetGrainExtensionMap } from "grain";
import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress";

interface Props {
    open: boolean
    onClose: (refresh: boolean) => void
    binKey: string;
    devices: Device[];
    binMode: pond.BinMode;
    grain?: pond.Grain;
    preferences?: Map<string, pond.BinComponentPreferences>;
    binCables?: GrainCable[];
    compDevMap?: Map<string, number>;
    presets?: DevicePreset[];
    startChange: () => void;
    changeComplete: () => void;
    defaultTargetMoisture?: number;
    changeTargetMoisture?: (newTarget: number) => void
    defaultOutdoorTemp?: number;
    changeOutdoorTemp?: (newTemp: number) => void
    defaultOutdoorHumidity?: number;
    changeOutdoorHumidity?: (newHumidity: number) => void
}

interface Option {
  label: string;
  device: Device;
  icon?: string;
}

interface DialogStep {
  label: string;
  completed?: boolean;
}

export default function ModeChangeDialog(props: Props){
  const { 
    open,
    onClose,
    binMode,
    devices,
    binKey,
    preferences,
    binCables,
    compDevMap,
    grain,
    startChange,
    changeComplete,
    defaultTargetMoisture,
    defaultOutdoorTemp,
    defaultOutdoorHumidity,
    changeTargetMoisture,
    changeOutdoorTemp,
    changeOutdoorHumidity,
    presets
  } = props
  const [{as, user}] = useGlobalState()
  const [componentSets, setComponentSets] = useState<ComponentSet[]>([])
  const interactionAPI = useInteractionsAPI()
  const componentAPI = useComponentAPI();
  const [selectedDevice, setSelectedDevice] = useState<Device | undefined>()
  const [existingInteractions, setExistingInteractions] = useState<Interaction[]>([])
  const [loading, setLoading] = useState(false)
  const [options, setOptions] = useState<Option[]>([])
  const [deviceComponents, setDeviceComponents] = useState<Map<string, Component[]>>(
    new Map<string, Component[]>()
  );

  const [plenums, setPlenums] = useState<Plenum[]>([])
  const [ambients, setAmbients] = useState<Ambient[]>([])
  const [heaters, setHeaters] = useState<Controller[]>([])
  const [fans, setFans] = useState<Controller[]>([])
  const grainExtensionMap = GetGrainExtensionMap();
  const [conflictingSetInteractions, setConflictingSetInteractions] = useState<Interaction[]>([])
  const [interactionsToAdd, setInteractionsToAdd] = useState<pond.MultiInteractionSettings>()
  const [promiseStages, setPromiseStages] = useState<Stage[]>([])
  const [dialogSteps, setDialogSteps] = useState<DialogStep[]>([])
  const [currentStep, setCurrentStep] = useState(0)
  const theme = useTheme()

  //the state variables for the moisture
  const [moistureInput, setMoistureInput] = useState<string>("")
  const [outdoorTempInput, setOutdoorTempInput] = useState<string>("")
  const [outdoorHumidityInput, setOutdoorHumidityInput] = useState<string>("")

  const [deviceOption, setDeviceOption] = useState<Option>()

  useEffect(()=>{
    setMoistureInput(defaultTargetMoisture?.toString() ?? "0")
    setOutdoorTempInput(defaultOutdoorTemp?.toString() ?? "0")
    setOutdoorHumidityInput(defaultOutdoorHumidity?.toString() ?? "0")
  },[defaultTargetMoisture, defaultOutdoorTemp, defaultOutdoorHumidity])
  
  //get the interactions and components for the device when it is selected from the dropdown
  useEffect(()=>{
    if (!binKey) return;
    if (!selectedDevice) return
    let comps = deviceComponents.get(selectedDevice.id().toString());
    if (!comps) {
      comps = [];
      setLoading(true);
      let interactionPromise = interactionAPI.listInteractionsByDevice(selectedDevice.id(), undefined, as);
      let componentPromise = componentAPI.list(selectedDevice.id(), undefined, [binKey], ["bin"], true, as);
      let dComponents = new Map<string, Component[]>();
      Promise.all([componentPromise, interactionPromise])
        .then(([compResp, interactionResp]) => {
          let d = pond.ListComponentsResponse.fromObject(compResp.data);
          d.components.forEach(comp => {
            let c = Component.create(comp);
            let deviceNumber = compResp.data.componentDevices[c.key()];
            comps!.push(c);
            let newDComponents = dComponents.get(deviceNumber.toString());
            if (newDComponents === undefined) newDComponents = [];
            newDComponents.push(c);
            dComponents.set(deviceNumber.toString(), newDComponents);
          });
          setDeviceComponents(dComponents);
          setExistingInteractions(interactionResp);
        })
        .finally(() => {
          setLoading(false);
        });
    }
  },[selectedDevice, binKey, componentAPI, interactionAPI, deviceComponents, as])
  
  //sort the components according to the bin preferences
  useEffect(()=>{
if (loading) return;
if (!selectedDevice) return;
    if (!deviceComponents.get(selectedDevice.id().toString())) return;
    var plenums: Plenum[] = [];
    var ambients: Ambient[] = [];
    //var grainCables: GrainCable[] = [];
    var heaters: Controller[] = [];
    var fans: Controller[] = [];
    deviceComponents.get(selectedDevice.id().toString())!.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)
          //   grainCables.push(GrainCable.create(comp));
          if (pref.type === pond.BinComponent.BIN_COMPONENT_HEATER) {
            let heater = Controller.create(comp);
            heater.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_OFF;
            heaters.push(heater);
          }
          if (pref.type === pond.BinComponent.BIN_COMPONENT_FAN) {
            let fan = Controller.create(comp)
            fan.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_OFF;
            fans.push(fan);
          }
        }
      }
    });
    setPlenums(plenums);
    setAmbients(ambients);
    setHeaters(heaters);
    setFans(fans);
  }, [loading, setPlenums, setHeaters, deviceComponents, selectedDevice, preferences]);

  useEffect(() => {
    let o: Option[] = [];
    devices.forEach(device => {
      let newOption: Option = {device: device, label: device.name()}
      if(deviceOption === undefined) {
        setDeviceOption(newOption)
        setSelectedDevice(device)
      }
      o.push(newOption);
    });
    setOptions(o);
  }, [devices, setOptions]);


  //use effect here to determine the steps based on the bin mode
  useEffect(() => {
    let steps: DialogStep[] = []
    switch(binMode){
      case pond.BinMode.BIN_MODE_STORAGE:
        steps.push({
          label: "Storage Settings"
        })
        break;
      case pond.BinMode.BIN_MODE_COOLDOWN:
        steps.push({
          label: "Set Components"
        })
        steps.push({
          label: "Progress"
        })
        break;
      case pond.BinMode.BIN_MODE_DRYING:
      case pond.BinMode.BIN_MODE_HYDRATING:
        steps.push({
          label: "Weather Conditions"
        })
        steps.push({
          label: "Set Components"
        })
        steps.push({
          label: "Progress"
        })
    }
    setDialogSteps(steps)
  },[binMode])

  const buildHeaterInteraction = (
    sensor: Plenum | Ambient,
    heater: Controller,
    customPreset?: DevicePreset
  ): pond.InteractionSettings => {
    let interaction = pond.InteractionSettings.create({
      source: sensor.location(),
      sink: heater.location(),
      schedule: pond.InteractionSchedule.create({
        timeOfDayStart: "00:00",
        timeOfDayEnd: "24:00",
        timezone: moment.tz.guess(),
        weekdays: moment.weekdays().map(d => lowerCase(d))
      }),
      notifications: pond.InteractionNotifications.create({
        reports: true
      }),
      result: pond.InteractionResult.create({
        type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
        value: 1
      })
    });

    let temp = 0;
    let hum = 0;
    //if a preset was selected use its temp/hum values
    if (customPreset !== undefined) {
      temp = customPreset.temp();
      hum = customPreset.hum();
    } else {
      //otherwise use default values
      switch(binMode){
        case pond.BinMode.BIN_MODE_COOLDOWN:
          temp = 10
          hum = 45
        break;
        case pond.BinMode.BIN_MODE_DRYING:
          if(grain){
            //get the values using the grain type
            let ext = grainExtensionMap.get(grain)
            if (ext) {
              temp = ext.setTempC
              hum = ext.targetMC
            }
          }else{//otherwise use the default drying interaction
            temp = 40
            hum = 20
          }
      }
    }

    let conditions = [];
    let humidityCondition = pond.InteractionCondition.create({
      measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
      comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
      value: Math.round(
        describeMeasurement(
          quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
          sensor.settings.type,
          sensor.settings.subtype,
          undefined,
          user
        ).toStored(hum)
      )
    });
    conditions.push(humidityCondition);

    //since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit
    if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
      temp = Math.round((temp * (9 / 5) + 32) * 100) / 100;
    }
    let tempVal = describeMeasurement(
      quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
      sensor.settings.type,
      sensor.settings.subtype,
      undefined,
      user
    ).toStored(temp);

    let tempCondition = pond.InteractionCondition.create({
      measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
      comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
      value: Math.round(tempVal) //and then round the converted value since interaction conditions wont take floats
    });
    conditions.push(tempCondition);
    interaction.conditions = conditions;
    //the the heater output to auto so that when the components are update it uses the new mode
    heater.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_AUTO
    return interaction;
  };

  const buildFanInteraction = (
    sensor: Plenum | Ambient,
    fan: Controller,
    tempComparison: "greater" | "less",
    humidityComparison: "greater" | "less",
    customPreset?: DevicePreset
  ) => {
    let interaction = pond.InteractionSettings.create({
      source: sensor.location(),
      sink: fan.location(),
      schedule: pond.InteractionSchedule.create({
        timeOfDayStart: "00:00",
        timeOfDayEnd: "24:00",
        timezone: moment.tz.guess(),
        weekdays: moment.weekdays().map(d => lowerCase(d))
      }),
      notifications: pond.InteractionNotifications.create({
        reports: true
      }),
      result: pond.InteractionResult.create({
        type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
        value: 1
      })
    });

    let tempPreset =  0;
    let humidityPreset =  0;

    //if a custom preset was selected use it
    if (customPreset !== undefined) {
      tempPreset = customPreset.temp();
      humidityPreset = customPreset.hum();
    } else {
      //otherwise use one of the defaults
      switch (binMode) {
        case pond.BinMode.BIN_MODE_COOLDOWN:
          tempPreset = 15;
          humidityPreset = 60;
          break;
        case pond.BinMode.BIN_MODE_DRYING:
          if(grain){
            //get the values using the grain type
            let ext = grainExtensionMap.get(grain)
            if (ext) {
              tempPreset = ext.setTempC
              humidityPreset = ext.targetMC
            }
          }else{//otherwise use the default drying interaction
            tempPreset = 40
            humidityPreset = 20
          }
          break;
        case pond.BinMode.BIN_MODE_HYDRATING:
          tempPreset = 25;
          humidityPreset = 60;
          break;
      }
    }

    let conditions = [];
    let fanConditionOne = pond.InteractionCondition.create({
      measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
      comparison:
        humidityComparison === "greater"
          ? quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
          : quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
      value: describeMeasurement(
        quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
        sensor.settings.type,
        sensor.settings.subtype,
        undefined,
        user
      ).toStored(humidityPreset)
    });
    conditions.push(fanConditionOne);

    //since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit
    if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
      tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100;
    }
    let tempVal = describeMeasurement(
      quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
      sensor.settings.type,
      sensor.settings.subtype,
      undefined,
      user
    ).toStored(tempPreset);

    let fanConditionTwo = pond.InteractionCondition.create({
      measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
      comparison:
        tempComparison === "greater"
          ? quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
          : quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
      value: Math.round(tempVal) //and then round the converted value since the interaction does not take decimals
    });
    conditions.push(fanConditionTwo);

    interaction.conditions = conditions;
    //set the output mode to auto in the fan so that when the components are updated it uses the new mode
    fan.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_AUTO 
    return interaction;
  };

  //this is what will use the conflicting interactions
  const buildPromises = () => {
    //if there is no device selected or there are no interactions to add, do nothing
    if(!selectedDevice || !interactionsToAdd) return

    //build the stages to pass into the PromiseProgress
    //stage one is to remove conflicting interactions
    let stages: Stage[] = []
    let stage1: Stage = {
      title: "Remove Conflicting Interactions",
      steps: []
    }
    conflictingSetInteractions.forEach(i => {
      let newStep: PromiseStep = {
        title: "Removing Interaction",
        promise: () => interactionAPI.removeInteraction(selectedDevice.id(), i.key(), as)
      }
      stage1.steps.push(newStep);
    });
    if(stage1.steps.length > 0){
      stages.push(stage1)
    }
    //stage two is to add the new interactions
    let stage2: Stage = {
      title: "Add New Interactions",
      steps: [
        {
          title: "Adding Interactions",
          promise: () => interactionAPI.addMultiInteractions(selectedDevice.id(), interactionsToAdd)
        }
      ]
    }
    if(stage2.steps.length > 0){
      stages.push(stage2)
    }

    //stage three is to update the controller components
    let stage3: Stage = {
      title: "Update Controllers",
      steps: []
    }
    componentSets.forEach(set => {
      set.controllers.forEach(controller => {
        let newStep: PromiseStep = {
          title: "Updating " + controller.name(),
          promise: () => componentAPI.update(selectedDevice.id(), controller.settings)
        }
        stage3.steps.push(newStep)
      })
    })
    if(stage3.steps.length > 0){
      stages.push(stage3)
    }
    //set those stages to a state variable
    setPromiseStages(stages)
  }


  //this function just takes in the sets as they change and creates the list of conflicting interactions and the settings to create the new ones and returns them
  const createInteractions = (sets: ComponentSet[]) => {
    if(!selectedDevice) return undefined //if there is no device that was selected, do nothing
    let multiInteractionSettings: pond.MultiInteractionSettings = pond.MultiInteractionSettings.create()
    let conflictingInteractions: Interaction[] = []
    let linkedComponents = deviceComponents.get(selectedDevice.id().toString());
    //loop through the sets
    sets.forEach((set) => {
      //loop through the controllers
      set.controllers.forEach(controller => {
        let customPreset = set.selectedPresets.get(controller.key())
        //filter the conflicting interactions for that controller
        let c = existingInteractions.filter(i => {
          let conflicting = false;
          if (linkedComponents) {
            linkedComponents.forEach(comp => {
              if (sameComponentID(comp.location(), i.settings.sink)) {
                conflicting = true;
              }
            });
          }
          return conflicting;
        });
        conflictingInteractions = conflictingInteractions.concat(c)
        switch(binMode){
          case pond.BinMode.BIN_MODE_COOLDOWN:
            // if the controller is a heater create heater interaction
            if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
              multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller, customPreset))
            }else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
                     controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){
              // if the controller is a fan create a fan interaction
              multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "less", customPreset))
            }
            break;
          case pond.BinMode.BIN_MODE_DRYING:
            // if the controller is a heater create heater interaction
            if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
              multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller, customPreset))
            }else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
                     controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){// if the controller is a fan
                //if it is a combination set using a heater as well just turn the fan on
                if(set.combo){
                  controller.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_ON
                }else{
                  //otherwise make a fan interaction
                  multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "greater", "less", customPreset))
                }
            }
            break;
          case pond.BinMode.BIN_MODE_HYDRATING:
            //check to make sure the controller is a fan, you cant hydrate with a heater, if the controller is a heater then it will not add an interaction
            if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
              controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){
                //create fan interaction
                multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "greater", customPreset))
              }
            break;
        }
      })
    })
    setComponentSets([...sets])
    return {
      conflicting: conflictingInteractions,
      toAdd: multiInteractionSettings
    }
  }

  const deviceSelector = () => {
    return (
      <Autocomplete
        disablePortal
        options={options}
        value={deviceOption}
        fullWidth
        getOptionLabel={option => option.label || ""}
        onChange={(_, newValue) => {
          if(newValue){
            setSelectedDevice(newValue.device);
          }
        }}
        renderInput={params => <TextField {...params} variant="outlined" label="Device" />}
      />
    )
  }

  const modeDescription = () => {
    switch(binMode){
      case pond.BinMode.BIN_MODE_DRYING:
        return "Drying"
      case pond.BinMode.BIN_MODE_HYDRATING:
        return "Hydrating"
      case pond.BinMode.BIN_MODE_COOLDOWN:
        return "Cooldown"
      case pond.BinMode.BIN_MODE_STORAGE:
        return "Storage"
      default:
        return ""
    }
  }



  const close = (refresh: boolean) => {
    //clear state data
    setCurrentStep(0)
    setInteractionsToAdd(undefined)
    setPromiseStages([])
    onClose(refresh)
  }

  //this step will only be used for drying and hydrating
  const moistureStep = () => {
    return (
      <Box>
        <DialogContentText style={{ paddingBottom: theme.spacing(0) }}>
          Updating the current grain moisture will calibrate the estimate for more accurate
          results. The outdoor temperature will have some effect on the estimate as well; consider
          updating with drastic changes in the weather or using a predicted average.
        </DialogContentText>
        <TextField
          label={"Grain Moisture"}
          value={moistureInput}
          error={isNaN(parseFloat(moistureInput))}
          helperText={isNaN(parseFloat(moistureInput)) ? "Must be a valid number" : ""}
          onChange={event => {
            setMoistureInput(event.target.value)
            if(changeTargetMoisture && !isNaN(parseFloat(event.target.value))){
              changeTargetMoisture(parseFloat(event.target.value))
            }
          }}
          InputProps={{
            endAdornment: <InputAdornment position="end">%</InputAdornment>
          }}
          style={{ marginTop: theme.spacing(2) }}
          fullWidth
          variant="outlined"
        />
        <Typography variant="body1" style={{ marginTop: theme.spacing(2) }}>
          Weather
        </Typography>
        <TextField
          label={"Outdoor Temperature"}
          value={outdoorTempInput}
          error={isNaN(parseFloat(outdoorTempInput))}
          helperText={isNaN(parseFloat(outdoorTempInput)) ? "Must be a valid number" : ""}
          onChange={event => {
            setOutdoorTempInput(event.target.value)
            if(changeOutdoorTemp && !isNaN(parseFloat(event.target.value))){
              let val = parseFloat(event.target.value)
              changeOutdoorTemp(val)
            }
          }}
          InputProps={{
            endAdornment: (
              <InputAdornment position="end">
                {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
                  ? "°F"
                  : "℃"}
              </InputAdornment>
            )
          }}
          style={{ marginTop: theme.spacing(2) }}
          fullWidth
          variant="outlined"
        />
        <TextField
          label={"Outdoor Humidity"}
          value={outdoorHumidityInput}
          error={isNaN(parseFloat(outdoorHumidityInput))}
          helperText={isNaN(parseFloat(outdoorHumidityInput)) ? "Must be a valid number" : ""}
          onChange={event => {
            setOutdoorHumidityInput(event.target.value)
            if(changeOutdoorHumidity && !isNaN(parseFloat(event.target.value))){
              changeOutdoorHumidity(parseFloat(event.target.value))
            }
          }}
          InputProps={{
            endAdornment: <InputAdornment position="end">%</InputAdornment>
          }}
          style={{ marginTop: theme.spacing(2) }}
          fullWidth
          variant="outlined"
        />
      </Box>
    )
  };

  const selectDeviceStep = () => {
    switch(binMode){
      case pond.BinMode.BIN_MODE_STORAGE:
        return (
          <Box>
            {binCables && binCables?.length > 0 && compDevMap && (
              <Box marginBottom={2}>
                <CableTopNodeSummary
                  binKey={binKey}
                  cables={binCables}
                  componentDevMap={compDevMap}
                />
              </Box>
            )}
            <DialogContentText>
              Select device to turn off controllers and end conditioning. Any interactions will be
              left alone. Heaters/Fans will be turned off.
            </DialogContentText>
            {deviceSelector()}
          </Box>
        )
      case pond.BinMode.BIN_MODE_DRYING:
      case pond.BinMode.BIN_MODE_COOLDOWN:
      case pond.BinMode.BIN_MODE_HYDRATING:
        return (
          <Box>
            <DialogContentText>
              {modeDescription()} control is handled by interactions between multiple components on the
              same device. Please choose which device and components will handle this bin's{" "}
              {modeDescription()}.
            </DialogContentText>
            {deviceSelector()}
            <ConditioningSelector 
              plenums={plenums} 
              ambients={ambients} 
              fans={fans} 
              heaters={heaters} 
              presets={presets}
              binMode={binMode}
              updateSets={(sets) => {
                //update the component sets that will be used for the interactions
                //when the sets change it will create the interactions and any conflicting ones that need to be removed will be put into a list
                //then when the submit button gets clicked it will use the conflicting list and toAdd to remove conflicting interactions and add the new ones
                let i = createInteractions(sets)
                if(i !== undefined){
                  setConflictingSetInteractions(i.conflicting)
                  setInteractionsToAdd(i.toAdd)
                }
              }}
            />
          </Box>
        )
    }
  }

  const progressStep = () => {
    return (
      <Box>
        <PromiseProgress 
          stages={promiseStages}
          description="These are the changes that will occur in order to change your bin mode with the given options. Press start to begin."
          failFast 
          onStart={() => {
            startChange()
          }}
          onComplete={()=>{
            close(true)
            setCurrentStep(0)//reset the stepper
            changeComplete()//change the boolean preventing more mode changes
          }}
        /> 
      </Box>
    )
  }

  const stepper = () => {
    return (
      <Stepper nonLinear activeStep={currentStep} style={{ width: "100%", marginBottom: theme.spacing(2) }}>
        {dialogSteps.map((dialogStep, i) => {
          const labelProps: {
            optional?: React.ReactNode;
          } = {};
          return (
            <Step key={i} completed={dialogStep.completed}>
              <StepLabel {...labelProps}>{dialogStep.label}</StepLabel>
            </Step>
          );
        })}
      </Stepper>
    );
  }

  const actions = () => {
    let buttons: JSX.Element[] = []
    //this button should be on every step of all of them
    buttons.push(
      <Button key={"close"} onClick={() => {close(false)}}>Close</Button>
    )
    // this button should appear if the step they are on is not 0 and not the last step
    if(currentStep !== 0 && currentStep !== dialogSteps.length-1){
      buttons.push(
        <Button key={"back"} onClick={()=>{setCurrentStep(currentStep-1)}}>Back</Button>
      )
    }
    // this button should appear if the step they are on is less than the last step -1
    if(currentStep < dialogSteps.length-2){
      buttons.push(
        <Button key={"next"} onClick={() => {setCurrentStep(currentStep+1)}}>Next</Button>
      )
    }
    if(((binMode === pond.BinMode.BIN_MODE_STORAGE || binMode === pond.BinMode.BIN_MODE_COOLDOWN) && currentStep === 0) || 
        (binMode === pond.BinMode.BIN_MODE_DRYING || binMode === pond.BinMode.BIN_MODE_HYDRATING) && currentStep === 1){
          buttons.push(
            <Button key={"confirm"} onClick={() => {
              //if the bin mode is storage, just update the controllers for the selected device and close
              if(binMode === pond.BinMode.BIN_MODE_STORAGE){
                close(true)
                if (selectedDevice) {
                  //update the controllers to be off for the selected device
                  heaters.forEach(heater => {
                    componentAPI
                      .update(selectedDevice.id(), heater.settings, undefined, undefined, as)
                      .then()
                      .catch();
                  });
                  fans.forEach(fan => {
                    componentAPI
                      .update(selectedDevice.id(), fan.settings, undefined, undefined, as)
                      .then()
                      .catch();
                  });
                }
              }else{//if it is cooldown, drying, or hydrating, it will need to go to the progress step and initiate all of the promises
                buildPromises()
                setCurrentStep(dialogSteps.length-1)//set the stepper to the progress step
              }
            }}>Confirm</Button>
          )
        }
    return buttons
  }

  const stepperContent = (step: number) => {
    switch(binMode){
      case pond.BinMode.BIN_MODE_STORAGE:
        return selectDeviceStep();
      case pond.BinMode.BIN_MODE_COOLDOWN:
        switch (step) {
          case 1:
            return progressStep();
          default:
            return selectDeviceStep();
        }
      case pond.BinMode.BIN_MODE_DRYING:
      case pond.BinMode.BIN_MODE_HYDRATING:
        switch (step) {
          case 1:
            return selectDeviceStep();
          case 2:
            return progressStep();
          default:
            return moistureStep();
        }
    }
  };

  return (
    <React.Fragment>
    <ResponsiveDialog open={open} onClose={() => {close(false)}}>
      <DialogTitle>
      {binMode === pond.BinMode.BIN_MODE_STORAGE ? "Entering Storage Mode" : "Choose Device for " + modeDescription() + " Method"}
      </DialogTitle>
      <DialogContent>
        {stepper()}
        {stepperContent(currentStep)}
      </DialogContent>
      <DialogActions>
        {actions()}
      </DialogActions>
    </ResponsiveDialog>
    </React.Fragment>
  )
}