import {
  Button,
  Checkbox,
  DialogActions,
  DialogContent,
  DialogTitle,
  Divider,
  FormControl,
  FormControlLabel,
  FormHelperText,
  FormLabel,
  Grid2 as Grid,
  IconButton,
  InputAdornment,
  MenuItem,
  Switch,
  TextField,
  Theme,
  Tooltip,
  Typography,
  useTheme
} from "@mui/material";
import AddIcon from "@mui/icons-material/AddCircle";
import RemoveIcon from "@mui/icons-material/RemoveCircle";
import classNames from "classnames";
import DeleteButton from "common/DeleteButton";
import ResponsiveDialog from "common/ResponsiveDialog";
import { coalesce, or, capitalize } from "utils";
import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks";
import { Component, Device, Interaction } from "models";
import moment from "moment-timezone";
import {
  componentIDToString,
  emptyComponentId,
  getComponentIDString,
  sameComponentID,
  stringToComponentId
} from "pbHelpers/Component";
import {
  extension,
  getMeasurements,
  GetNumNodes,
  hasInteractionResultType,
  isSource
} from "pbHelpers/ComponentType";
import { ComponentType, Measurement, Operator, Result } from "pbHelpers/Enums";
import {
  getDefaultInteraction,
  isInteractionValid,
  isMeasurementTypeValid,
  isSourceValid,
  timeOfDayDescriptor
} from "pbHelpers/Interaction";
import { describeMeasurement, MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useGlobalState } from "providers";
import React, { useCallback, useEffect, useState } from "react";
import { hasDeviceFeature } from "services/feature/service";
import RemoveInteraction from "./RemoveInteraction";
import { makeStyles } from "@mui/styles";
import { green, red } from "@mui/material/colors";
import { LocalizationProvider, TimePicker } from "@mui/x-date-pickers";
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
// import dayjs from "dayjs";
import CancelSubmit from "common/CancelSubmit";
import { Sensor } from "ventilation/drawable/Sensor";

// import { Sensor } from "ventilation/drawable/Sensor";

const useStyles = makeStyles((theme: Theme) => {
  return ({
    dialogContent: {
      marginTop: theme.spacing(1)
    },
    borderedContainer: {
      padding: theme.spacing(1),
      marginBottom: theme.spacing(2),
      border: "1px solid rgba(255, 255, 255, 0.12)",
      borderRadius: "4px"
    },
    greenButton: {
      color: green["600"]
    },
    redButton: {
      color: red["600"]
    },
    noPadding: {
      padding: 0
    },
    forPrompt: {
      fontSize: "0.875em",
      marginTop: "10px"
    },
    timeRange: {
      fontSize: "0.875em",
      marginTop: "20px"
    },
    singleInput: {
      minHeight: "48px"
    }
  })
});

interface Props {
  device: Device;
  initialComponent?: Component;
  initialInteraction?: Interaction;
  components: Component[];
  mode?: string;
  isDialogOpen: boolean;
  closeDialogCallback: Function;
  refreshCallback: Function;
  canEdit?: boolean;
  sensor?: Sensor;
}

export default function InteractionSettings(props: Props) {
  const {
    device,
    initialComponent,
    initialInteraction,
    components,
    mode,
    isDialogOpen,
    closeDialogCallback,
    refreshCallback,
    canEdit,
    sensor
  } = props;
  const theme = useTheme();
  const { success, error } = useSnackbar();
  const prevInitialInteraction = usePrevious(initialInteraction);
  const prevComponents = usePrevious(components);
  const prevInitialComponent = usePrevious(initialComponent);
  const classes = useStyles();
  const [{ user, as }] = useGlobalState();
  const interactionsAPI = useInteractionsAPI();
  const [interaction, setInteraction] = useState<Interaction>(
    Interaction.clone(initialInteraction)
  );
  const [isRemoveInteractionOpen, setIsRemoveInteractionOpen] = useState<boolean>(false);
  const [mappedComponents, setMappedComponents] = useState<Map<string, Component>>(new Map());
  const [numConditions, setNumConditions] = useState<number>(0);
  const [rawConditionValues, setRawConditionValues] = useState<string[]>([]);
  const [rawResultValue, setRawResultValue] = useState<string>("");
  const [dutyCycleEnabled, setDutyCycleEnabled] = useState<boolean>(false);
  const [dutyCycle, setDutyCycle] = useState<string>("");
  const [timezone, setTimezone] = useState<string | undefined>(undefined);
  //whether is any node(0), average of all nodes(1), single node(2), or node diff(3)
  const [subtypeDropdown, setSubtypeDropdown] = useState<number>(0);

  const initialConditions = useCallback(
    (type: quack.ComponentType): pond.InteractionCondition[] => {
      let measurementType = availableMeasurementTypes(type)[0];
      let condition = pond.InteractionCondition.create({
        measurementType: measurementType,
        comparison: measurementType === Measurement.boolean ? Operator.equals : Operator.less,
        value: 0
      });
      return [condition];
    },
    []
  );

  const setDefaultState = useCallback(() => {
    let interaction = getDefaultInteraction();
    if (initialInteraction && mode === "update") {
      interaction = initialInteraction;
      if (interaction.settings.subtype === 0 || interaction.settings.subtype === 1) {
        setSubtypeDropdown(interaction.settings.subtype);
      } else {
        if (interaction.settings.subtype >= 138) {
          setSubtypeDropdown(4);
        } else if (interaction.settings.subtype > 17) {
          setSubtypeDropdown(3);
        } else if (interaction.settings.subtype > 1) {
          setSubtypeDropdown(2);
        }
      }
    }
    if (initialComponent && mode === "add") {
      interaction.settings.result = pond.InteractionResult.create({
        type: Result.report,
        value: 0,
        mode: 0
      });
      if (isSource(initialComponent.settings.type)) {
        interaction.settings.source = initialComponent.location();
        interaction.settings.conditions = initialConditions(initialComponent.settings.type);
      }
    }
    let rawConditionValues = [] as Array<string>;
    interaction.settings.conditions.forEach((condition: pond.InteractionCondition) => {
      let value = describeMeasurement(
        condition.measurementType,
        or(initialComponent, Component.create()).settings.type,
        or(initialComponent, Component.create()).settings.subtype
      ).toDisplay(condition.value);
      rawConditionValues.push(value.toString());
    });
    let numConditions = interaction.settings.conditions.length;
    let mappedComponents: Map<string, Component> = new Map();
    components.forEach(component => mappedComponents.set(component.locationString(), component));
    let interactionResult = pond.InteractionResult.create(
      or(interaction.settings.result, undefined)
    );

    if (sensor) {
      interaction.settings.subtype = 2;
      interaction.settings.nodeOne = sensor.index;
      interaction.settings.nodeTwo = sensor.index;
    }

    setIsRemoveInteractionOpen(false);
    setNumConditions(numConditions);
    setRawConditionValues(rawConditionValues);
    setRawResultValue(interactionResult.value.toString());
    setDutyCycleEnabled(interactionResult.dutyCycle > 0);
    setDutyCycle(interactionResult.dutyCycle.toString());
    setMappedComponents(mappedComponents);
    setInteraction(interaction);
  }, [components, initialComponent, initialConditions, initialInteraction, mode, /*sensor*/]);

  const availableSources = () => {
    return components.filter(component => isSource(component.settings.type));
  };

  useEffect(() => {
    if (user && user.settings.timezone) {
      setTimezone(user.settings.timezone);
    }
    if (
      prevInitialInteraction !== initialInteraction ||
      (prevComponents && prevComponents.length !== components.length) ||
      getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent)
    ) {
      setDefaultState();
    }
  }, [
    components.length,
    initialComponent,
    initialInteraction,
    prevComponents,
    prevInitialComponent,
    prevInitialInteraction,
    setDefaultState,
    user
  ]);

  const getAvailableSinks = () => {
    let type = or(interaction.settings.result, pond.InteractionResult.create()).type;
    return components.filter(component => hasInteractionResultType(component.settings.type, type));
  };

  const close = () => {
    closeDialogCallback();
    if (mode === "add") {
      setDefaultState();
    }
  };

  const submit = () => {
    let settings = interaction.settings;
    if (multiNodeSource() && (settings.nodeOne > 0 || settings.nodeTwo > 0)) {
      if (subtypeDropdown === 3 || subtypeDropdown === 2) {
        settings.subtype = interaction.subtypeFromNodes(settings.nodeOne, settings.nodeTwo);
        if (
          interaction.settings.nodeOne > interaction.settings.nodeTwo &&
          interaction.settings.nodeTwo !== 0
        ) {
          //flip operator and send negative comparitor to save
          settings.conditions.forEach(condition => {
            if (
              condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
            ) {
              condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN;
            } else if (
              condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN
            ) {
              condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN;
            }
            condition.value = condition.value * -1;
          });
        }
      } else if (subtypeDropdown === 4) {
        settings.subtype = Interaction.upToSubtype(settings.nodeOne);
      }
    } else {
      settings.subtype = subtypeDropdown;
    }

    let sync = true;
    if (interaction)
      Object.keys(interaction?.settings).forEach(key => {
        // Check if the key value has changed
        let k = key as keyof pond.InteractionSettings;
        let initial = JSON.stringify(initialInteraction?.settings[k]);
        let after = JSON.stringify(interaction.settings[k]);
        if (initial !== after) {
          if (key !== "sortPriority") {
            sync = false;
          }
        }
      });

    switch (mode) {
      case "add":
        interactionsAPI
          .addInteraction(Number(device.settings.deviceId), settings, as)
          .then(() => {
            success("Interaction was successfully created");
            refreshCallback();
          })
          .catch(() => error("Error occured when creating an interaction"))
          .finally(() => close());
        break;
      default:
        if (sync) {
          let s = pond.InteractionPondSettings.create();
          s.sortPriority = interaction.settings.sortPriority;
          interactionsAPI
            .updateInteractionPondSettings(Number(device.settings.deviceId), interaction.key(), s, as)
            .then(() => {
              success("Interaction pond settings were successfully updated");
              refreshCallback();
            })
            .catch(() => error("Error when updating interaction pond settings"))
            .finally(() => close());
        } else {
          interactionsAPI
            .updateInteraction(Number(device.settings.deviceId), settings, as)
            .then(() => {
              success("Interaction was successfully updated");
              refreshCallback();
            })
            .catch(() => error("Error occured when updating an interaction"))
            .finally(() => close());
        }
        break;
    }
  };

  const openRemoveInteraction = () => {
    setIsRemoveInteractionOpen(true);
  };

  const closeRemoveInteraction = () => {
    setIsRemoveInteractionOpen(false);
    close();
  };

  const interactionRemoved = () => {
    refreshCallback();
  };

  const addCondition = () => {
    let updatedInteraction = Interaction.clone(interaction);
    let updatedRawConditionValues = rawConditionValues;
    if (numConditions < 2 && interaction.settings.source) {
      let condition = pond.InteractionCondition.create();
      condition.measurementType = getMeasurements(
        interaction.settings.source.type
      )[0].measurementType;
      condition.comparison =
        condition.measurementType === Measurement.boolean ? Operator.equals : Operator.less;
      updatedRawConditionValues.push("");
      updatedInteraction.settings.conditions.push(condition);

      setInteraction(updatedInteraction);
      setNumConditions(numConditions + 1);
      setRawConditionValues(updatedRawConditionValues);
    }
  };

  const removeCondition = (index: number) => {
    let updatedInteraction = Interaction.clone(interaction);
    let updatedRawConditionValues = rawConditionValues;
    if (numConditions > 1 && index < numConditions) {
      updatedInteraction.settings.conditions.splice(index, 1);
      updatedRawConditionValues.splice(index, 1);
      setInteraction(updatedInteraction);
      setNumConditions(numConditions - 1);
      setRawConditionValues(updatedRawConditionValues);
    }
  };

  const availableMeasurementTypes = (type: quack.ComponentType): quack.MeasurementType[] => {
    return getMeasurements(type).map(m => m.measurementType);
  };

  //changing the source also resets the conditions
  const changeSource = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let changed = event.target.value !== componentIDToString(interaction.settings.source);
    if (!changed) return;
    updatedInteraction.settings.source = stringToComponentId(event.target.value.toString());
    updatedInteraction.settings.conditions = initialConditions(
      or(interaction.settings.source, quack.ComponentID.create()).type
    );
    let result = pond.InteractionResult.create(or(interaction.settings.result, undefined));
    if (result.type === Result.report) {
      updatedInteraction.settings.sink = emptyComponentId();
    }
    updatedInteraction.settings.result = result;
    setInteraction(updatedInteraction);
    setNumConditions(1);
    setRawConditionValues([""]);
  };

  const changePriority = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    updatedInteraction.settings.priority = Number(event.target.value);
    setInteraction(updatedInteraction);
  };

  const changeSortPriority = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    updatedInteraction.settings.sortPriority = Number(event.target.value);
    setInteraction(updatedInteraction);
  };

  const describeSource = (measurementType: quack.MeasurementType): MeasurementDescriber => {
    const source: Component = or(
      mappedComponents.get(componentIDToString(interaction.settings.source)),
      Component.create()
    );
    return describeMeasurement(measurementType, source.settings.type, source.settings.subtype);
  };

  const describeSink = (): MeasurementDescriber => {
    const sink = mappedComponents.get(componentIDToString(interaction.settings.sink));
    return describeMeasurement(
      Measurement.boolean,
      or(sink, Component.create()).settings.type,
      or(sink, Component.create()).settings.subtype
    );
  };

  const changeMeasurementType = (conditionIndex: number, event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let measurementType = interaction.settings.conditions[conditionIndex].measurementType;
    let rawValue = rawConditionValues[conditionIndex] as any;
    updatedInteraction.settings.conditions[conditionIndex].value = describeSource(
      measurementType
    ).toStored(rawValue);
    updatedInteraction.settings.conditions[conditionIndex].measurementType = event.target.value;
    if (event.target.value === Measurement.boolean) {
      updatedInteraction.settings.conditions[conditionIndex].comparison = Operator.equals;
    }
    setInteraction(updatedInteraction);
  };

  const changeComparison = (conditionIndex: number, event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    updatedInteraction.settings.conditions[conditionIndex].comparison = event.target.value;
    setInteraction(updatedInteraction);
  };

  const changeValue = (conditionIndex: number, event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let updatedRawConditionValues = rawConditionValues;
    let measurementType = interaction.settings.conditions[conditionIndex].measurementType;
    updatedRawConditionValues[conditionIndex] = event.target.value;
    updatedInteraction.settings.conditions[conditionIndex].value = describeSource(
      measurementType
    ).toStored(event.target.value);
    setInteraction(updatedInteraction);
    setRawConditionValues(updatedRawConditionValues);
  };

  const changeResultValue = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let value = event.target.value;
    if (event.target.type === "checkbox") {
      value = event.target.checked ? 1 : 0;
    }
    (updatedInteraction.settings.result as pond.InteractionResult).value = isNaN(value)
      ? 0
      : Number(value);
    setInteraction(updatedInteraction);
    setRawResultValue(value);
  };

  const changeSubtype = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let value = event.target.value;
    if (!isNaN(value)) {
      if (value < 2) {
        updatedInteraction.settings.subtype = value;
      }
      setSubtypeDropdown(value);
      updatedInteraction.settings.nodeOne = 0;
      updatedInteraction.settings.nodeTwo = 0;
    }
    setInteraction(updatedInteraction);
  };

  const changeNode = (event: any, node: number) => {
    let updatedInteraction = Interaction.clone(interaction);
    let value = event.target.value;
    if (!isNaN(value)) {
      if (node === 1) {
        updatedInteraction.settings.nodeOne = value;
      } else if (node === 2) {
        updatedInteraction.settings.nodeTwo = value;
      }
    }
    setInteraction(updatedInteraction);
  };

  const changeResultType = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let result = interaction.settings.result as pond.InteractionResult;
    result.type = event.target.value;
    if (result.type === Result.report) {
      updatedInteraction.settings.sink = null;
    } else if (result.type === Result.toggle) {
      let availableSinks = getAvailableSinks();
      if (availableSinks.length > 0) {
        let firstSink = availableSinks[0];
        updatedInteraction.settings.sink = quack.ComponentID.fromObject({
          type: firstSink.settings.type,
          addressType: firstSink.settings.addressType,
          address: firstSink.settings.address
        });
      }
    }
    updatedInteraction.settings.result = result;
    setInteraction(updatedInteraction);
  };

  const changeResultMode = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let result = interaction.settings.result as pond.InteractionResult;
    result.mode = event.target.value;
    updatedInteraction.settings.result = result;
    setInteraction(updatedInteraction);
  };

  const changeSink = (event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    updatedInteraction.settings.sink = stringToComponentId(event.target.value.toString());
    if (
      updatedInteraction.settings.sink.type === quack.ComponentType.COMPONENT_TYPE_INTERNAL_FUNCTION
    ) {
      if (
        updatedInteraction.settings.sink.addressType ===
        quack.AddressType.ADDRESS_TYPE_INTERNAL_SLEEP
      ) {
        if (updatedInteraction.settings.result) {
          updatedInteraction.settings.result.value = 1;
        }
      }
    }
    setInteraction(updatedInteraction);
  };

  const toggleReport = (_event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let notifications = or(
      interaction.settings.notifications,
      pond.InteractionNotifications.create()
    );
    notifications.reports = !notifications.reports;
    updatedInteraction.settings.notifications = notifications;
    setInteraction(updatedInteraction);
  };

  const toggleNotify = (_event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let notifications = or(
      updatedInteraction.settings.notifications,
      pond.InteractionNotifications.create()
    );
    notifications.notify = !notifications.notify;
    updatedInteraction.settings.notifications = notifications;
    setInteraction(updatedInteraction);
  };

  const toggleDaySelected = (day: string, event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let schedule = or(updatedInteraction.settings.schedule, pond.InteractionSchedule.create());
    if (event.target.checked) {
      if (!schedule.weekdays.includes(day)) {
        schedule.weekdays.push(day);
      }
    } else {
      schedule.weekdays.splice(schedule.weekdays.indexOf(day), 1);
    }
    updatedInteraction.settings.schedule = schedule;
    setInteraction(updatedInteraction);
  };

  const setScheduleTime = (id: string, event: any) => {
    let updatedInteraction = Interaction.clone(interaction);
    let schedule = or(updatedInteraction.settings.schedule, pond.InteractionSchedule.create());
    if (id === "shortcut") {
      let value = event.target.value;
      if (value === "allDay") {
        schedule.timeOfDayStart = "00:00";
        schedule.timeOfDayEnd = "24:00";
      } else if (value === "before") {
        schedule.timeOfDayStart = "00:00";
        if (!schedule.timeOfDayEnd || schedule.timeOfDayEnd === "24:00") {
          schedule.timeOfDayEnd = "23:59";
        }
      } else if (value === "after") {
        schedule.timeOfDayEnd = "24:00";
        if (schedule.timeOfDayStart === "00:00") {
          schedule.timeOfDayStart = "00:01";
        }
      } else {
        if (schedule.timeOfDayStart === "00:00") {
          schedule.timeOfDayStart = "00:01";
        }
        if (schedule.timeOfDayEnd === "24:00") {
          schedule.timeOfDayEnd = "23:59";
        }
      }
    } else {
      let timeOfDay =
        event
          .hour()
          .toString()
          .padStart(2, "0") +
        ":" +
        event
          .minute()
          .toString()
          .padStart(2, "0");
      if (id === "start") {
        schedule.timeOfDayStart = timeOfDay;
      } else if (id === "end") {
        if (timeOfDay === "00:00") {
          timeOfDay = "24:00";
        }
        schedule.timeOfDayEnd = timeOfDay;
      }
    }
    schedule.timezone = or(timezone, moment.tz.guess());
    updatedInteraction.settings.schedule = schedule;
    setInteraction(updatedInteraction);
  };

  const isInitialComponentTheSource = () => {
    if (!interaction || !initialComponent) return false;
    return sameComponentID(interaction.settings.source, initialComponent.location());
  };

  const isInitialComponentTheSink = () => {
    if (!interaction || !initialComponent) return false;
    let sinkID = JSON.stringify(quack.ComponentID.create(or(interaction.settings.sink, undefined)));
    let component = initialComponent;
    let componentID = JSON.stringify(
      quack.ComponentID.create({
        type: component.settings.type,
        addressType: component.settings.addressType,
        address: component.settings.address
      })
    );
    return sinkID === componentID;
  };

  const invalidConditionValue = (index: number) => {
    const conditions = interaction.settings.conditions;
    if (conditions.length <= index) return false;
    if (conditions[index].measurementType === Measurement.boolean) {
      return false;
    }
    let value = rawConditionValues[index];
    return value === "" || isNaN(Number(value));
  };

  const invalidConditionValues = () => {
    let invalid = false;
    interaction.settings.conditions.forEach((_, i) => {
      if (invalidConditionValue(i)) {
        invalid = true;
      }
    });
    return invalid;
  };

  const invalidResultValue = () => {
    return isNaN(Number(rawResultValue));
  };

  const getNodeOptions = (describer?: MeasurementDescriber) => {
    let options = [];
    let numNodes = 0;
    if (initialComponent) {
      if (initialComponent.lastMeasurement[0] && initialComponent.lastMeasurement[0].values[0]) {
        numNodes = initialComponent.lastMeasurement[0].values[0].values.length; //use the new structure last measurement to determine the number of nodes
      } else {
        numNodes = GetNumNodes(
          initialComponent.type(),
          initialComponent.status.lastMeasurement?.measurement
        );
      }
    }

    for (let i = 0; i <= numNodes; i++) {
      let label = i === 0 ? "None" : "Node " + i;
      if (describer && i > 0) {
        let details = describer.nodeDetails();
        if (details && details.labels[i - 1]) {
          label = details.labels[i - 1];
        }
      }
      options.push(
        <MenuItem key={i} value={i}>
          {label}
        </MenuItem>
      );
    }
    return options;
  };

  //determine if the source component has nodes
  const multiNodeSource = () => {
    return getNodeOptions().length > 1;
  };

  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UI BEGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  const title = () => {
    return (
      <React.Fragment>
        <Grid container justifyContent="space-between">
          <Grid >
            {mode && mode === "add" ? "Add Interaction" : "Interaction Settings"}
            {/* <Typography variant="body2" color="textSecondary">
              {sensor ? "Subnode: " + sensor.name() : device.name()}
            </Typography> */}
          </Grid>
        </Grid>
      </React.Fragment>
    );
  };

  const sourceInput = () => {
    return (
      <TextField
        select
        id="source"
        label="Source"
        value={componentIDToString(interaction.settings.source)}
        onChange={changeSource}
        fullWidth
        autoFocus={false}
        margin="normal"
        variant="outlined"
        InputLabelProps={{ shrink: true }}
        disabled={isInitialComponentTheSource() || !canEdit || mode === "update"}
        SelectProps={{ className: classes.singleInput }}>
        {sourceMenuItems()}
      </TextField>
    );
  };

  const priorityInput = () => {
    return (
      <TextField
        select
        id="priority"
        label="Priority"
        value={interaction.settings.priority}
        onChange={changePriority}
        fullWidth
        autoFocus={false}
        margin="normal"
        variant="outlined"
        InputLabelProps={{ shrink: true }}
        disabled={!canEdit}
        SelectProps={{ className: classes.singleInput }}>
        {priorityMenuItems()}
      </TextField>
    );
  };

  const sortingInput = () => {
    return (
      <TextField
        select
        id="sorting"
        label="Sorting Priority"
        value={interaction.settings.sortPriority}
        onChange={changeSortPriority}
        fullWidth
        autoFocus={false}
        margin="normal"
        variant="outlined"
        InputLabelProps={{ shrink: true }}
        disabled={!canEdit}
        SelectProps={{ className: classes.singleInput }}>
        {sortingMenuItems()}
      </TextField>
    );
  };

  const measurementTypeMenuItems = () => {
    const type = or(interaction.settings.source, quack.ComponentID.create()).type;
    return availableMeasurementTypes(type).map(measurementType => (
      <MenuItem key={measurementType} value={measurementType}>
        {describeSource(measurementType).label()}
      </MenuItem>
    ));
  };

  const sourceMenuItems = () => {
    return availableSources().map(source => (
      <MenuItem key={source.locationString()} value={source.locationString()}>
        {source.name()}
      </MenuItem>
    ));
  };

  const sinkMenuItems = () => {
    let sinks = getAvailableSinks().map(sink => (
      <MenuItem key={sink.locationString()} value={sink.locationString()}>
        {sink.name()}
      </MenuItem>
    ));
    if (isResultType(Result.set)) {
      sinks.push(
        <MenuItem key="30-16-1" value="30-16-1">
          Stay Awake
        </MenuItem>
      );
    }
    return sinks;
  };

  const priorityMenuItems = () => {
    return ["Low", "Medium", "High"].map((name: string, i: number) => (
      <MenuItem key={i} value={i}>
        {name}
      </MenuItem>
    ));
  };

  const sortingMenuItems = () => {
    return ["None", "1", "2", "3", "4", "5"].map((name: string, i: number) => (
      <MenuItem key={i} value={i}>
        {name}
      </MenuItem>
    ));
  };

  const isBooleanValue = (index: number) => {
    if (interaction.settings.conditions.length <= index) return false;
    return interaction.settings.conditions[index].measurementType === Measurement.boolean;
  };

  const conditionValue = (index: number, interaction: Interaction, values: string[]) => {
    let condition = interaction.settings.conditions[index];
    return isBooleanValue(index) ? condition.value.toString() : values[index];
  };

  const conditionGroup = (index: number) => {
    if (index >= numConditions) return;
    let measurementType = interaction.settings.conditions[index].measurementType;
    let source = describeSource(measurementType);
    let isBoolean = isBooleanValue(index);
    return (
      <React.Fragment key={index}>
        <Grid
          key={"interaction"}
          container
          direction="row"
          justifyContent="center"
          alignItems="center"
          spacing={2}>
          {index > 0 && (
            <Grid size={{ xs: 12 }}>
              <Typography align="center">AND</Typography>
            </Grid>
          )}
          <Grid size={{ xs: 10, sm: 4 }}>
            <TextField
              select
              id="measurementType"
              required={true}
              label=""
              helperText="Measurement"
              error={
                !isMeasurementTypeValid(interaction.settings.conditions[index].measurementType)
              }
              disabled={!isSourceValid(interaction.settings.source) || !canEdit}
              value={interaction.settings.conditions[index].measurementType}
              onChange={event => changeMeasurementType(index, event)}
              autoFocus={false}
              margin="dense"
              variant="standard"
              fullWidth
              InputLabelProps={{ shrink: true }}>
              {measurementTypeMenuItems()}
            </TextField>
          </Grid>
          <Grid size={{ xs: 10, sm: 3 }}>
            <TextField
              select
              id="comparison"
              required={true}
              label=""
              helperText="Comparator"
              disabled={!isSourceValid(interaction.settings.source) || isBoolean || !canEdit}
              value={interaction.settings.conditions[index].comparison}
              onChange={event => changeComparison(index, event)}
              autoFocus={false}
              margin="dense"
              variant="standard"
              fullWidth
              InputLabelProps={{ shrink: true }}>
              {!isBoolean && [
                <MenuItem key={2} value={Operator.less}>
                  {"is below"}
                </MenuItem>,
                <MenuItem key={3} value={Operator.greater}>
                  {"is above"}
                </MenuItem>
              ]}
              <MenuItem key={1} value={Operator.equals}>
                {"is exactly"}
              </MenuItem>
            </TextField>
          </Grid>
          <Grid size={{ xs: 8, sm: 4 }}>
            <TextField
              select={isBoolean}
              id="value"
              required={true}
              type={"text"}
              label=""
              helperText="Value"
              error={invalidConditionValue(index)}
              disabled={!isSourceValid(interaction.settings.source) || !canEdit}
              value={conditionValue(index, interaction, rawConditionValues)}
              onChange={event => changeValue(index, event)}
              autoFocus={false}
              margin="dense"
              variant="standard"
              fullWidth
              InputProps={{
                endAdornment: <InputAdornment position="end">{source.unit()}</InputAdornment>
              }}
              InputLabelProps={{ shrink: true }}>
              {isBoolean && [
                <MenuItem key={0} value={0}>
                  {source.enumerations()[0]}
                </MenuItem>,
                <MenuItem key={1} value={1}>
                  {source.enumerations()[1]}
                </MenuItem>
              ]}
            </TextField>
          </Grid>
          <Grid size={{ xs: 2, sm: 1 }}>
            {index === 0 ? (
              <Tooltip title="Add another condition">
                <IconButton
                  color="primary"
                  disabled={numConditions > 1 || !canEdit}
                  aria-label="Add another condition"
                  onClick={addCondition}
                  className={classNames(classes.greenButton, classes.noPadding)}>
                  <AddIcon />
                </IconButton>
              </Tooltip>
            ) : (
              <Tooltip title="Remove this condition">
                <IconButton
                  color="default"
                  disabled={!canEdit}
                  aria-label="Remove condition"
                  onClick={() => removeCondition(index)}
                  className={classNames(classes.redButton, classes.noPadding)}>
                  <RemoveIcon />
                </IconButton>
              </Tooltip>
            )}
          </Grid>
        </Grid>
        {multiNodeSource() && !sensor && (
          <Grid
            key={"nodes"}
            container
            direction="row"
            justifyContent="center"
            alignItems="center"
            spacing={2}>
            <Grid size={{ xs: 10, sm: 4 }}>
              <TextField
                select
                id="subtype"
                label="Subtype"
                disabled={!isSourceValid(interaction.settings.source) || !canEdit}
                value={subtypeDropdown}
                onChange={event => changeSubtype(event)}
                margin="dense"
                variant="standard"
                fullWidth
                InputLabelProps={{ shrink: true }}>
                <MenuItem key={0} value={0}>
                  Any
                </MenuItem>
                <MenuItem key={1} value={1}>
                  Average
                </MenuItem>
                <MenuItem key={2} value={2}>
                  Single Node
                </MenuItem>
                <MenuItem key={3} value={3}>
                  Node Diff
                </MenuItem>
                <MenuItem key={4} value={4}>
                  Up To
                </MenuItem>
              </TextField>
            </Grid>
            <Grid size={{ xs: 10, sm: 4 }}>
              {(subtypeDropdown === 2 || subtypeDropdown === 3 || subtypeDropdown === 4) && (
                <TextField
                  select
                  id="interactionType"
                  label="Node 1"
                  disabled={!isSourceValid(interaction.settings.source) || !canEdit}
                  value={interaction.settings.nodeOne}
                  onChange={event => changeNode(event, 1)}
                  margin="dense"
                  variant="standard"
                  fullWidth
                  InputLabelProps={{ shrink: true }}>
                  {getNodeOptions(source)}
                </TextField>
              )}
            </Grid>
            <Grid size={{ xs: 10, sm: 4 }}>
              {subtypeDropdown === 3 && (
                <TextField
                  select
                  id="interactionType"
                  label="Node 2"
                  disabled={!isSourceValid(interaction.settings.source) || !canEdit}
                  value={interaction.settings.nodeTwo}
                  onChange={event => changeNode(event, 2)}
                  margin="dense"
                  variant="standard"
                  fullWidth
                  InputLabelProps={{ shrink: true }}>
                  {getNodeOptions(source)}
                </TextField>
              )}
            </Grid>
          </Grid>
        )}
      </React.Fragment>
    );
  };

  const conditionInput = () => {
    const conditionGroups = [];
    for (let i = 0; i < numConditions; i++) {
      conditionGroups[i] = conditionGroup(i);
    }
    return (
      <FormControl
        component={"fieldset" as "div"}
        className={classes.borderedContainer}
        fullWidth
        disabled={!isSourceValid(interaction.settings.source) || !canEdit}>
        <FormLabel component={"legend" as "caption"}>Conditions</FormLabel>
        {isSourceValid(interaction.settings.source) ? (
          <React.Fragment>{conditionGroups}</React.Fragment>
        ) : (
          <FormHelperText>You must select a source before adding conditions</FormHelperText>
        )}
      </FormControl>
    );
  };

  const isResultType = (type: quack.InteractionResultType): boolean => {
    return or(interaction.settings.result, pond.InteractionResult.create()).type === type;
  };

  const reportingEnabled = () => {
    return (
      or(interaction.settings.notifications, pond.InteractionNotifications.create()).reports ||
      or(interaction.settings.result, pond.InteractionResult.create()).type === Result.report
    );
  };

  const toggleDutyCycle = (event: any) => {
    let updatedInteraction = interaction;
    if (!interaction.settings.result) {
      updatedInteraction.settings.result = pond.InteractionResult.create();
    }
    if (!event.target.checked) {
      if (!updatedInteraction.settings.result) {
        updatedInteraction.settings.result = pond.InteractionResult.create({
          dutyCycle: 0
        });
      } else {
        updatedInteraction.settings.result.dutyCycle = 0;
      }
    }
    setDutyCycleEnabled(event.target.checked);
    setInteraction(updatedInteraction);
  };

  const changeDutyCycle = (event: any) => {
    let dutyCycle = Number(event.target.value);
    let updatedInteraction = interaction;
    if (!isNaN(dutyCycle)) {
      if (!updatedInteraction.settings.result) {
        updatedInteraction.settings.result = pond.InteractionResult.create();
      } else {
        updatedInteraction.settings.result.dutyCycle = dutyCycle;
      }
    }
    setDutyCycle(event.target.value);
    setInteraction(updatedInteraction);
  };

  const dutyCycleInput = () => {
    return (
      <React.Fragment>
        <Grid size={{ xs: 4, sm: 4 }}>
          <FormControlLabel
            control={
              <Checkbox
                id="enableDutyCycle"
                checked={dutyCycleEnabled}
                onChange={toggleDutyCycle}
                value="enableDutyCycle"
                color="secondary"
              />
            }
            label="Once every"
            disabled={!canEdit}
          />
        </Grid>
        <Grid size={{ xs: 8, sm: 8 }}>
          <TextField
            id="dutyCycle"
            value={dutyCycle}
            onChange={changeDutyCycle}
            fullWidth
            autoFocus={false}
            margin="dense"
            variant="standard"
            disabled={!canEdit || !dutyCycleEnabled}
            error={isNaN(Number(dutyCycle)) || Number(dutyCycle) < 0}
            InputLabelProps={{
              shrink: true
            }}
            InputProps={{
              endAdornment: <InputAdornment position="end">seconds</InputAdornment>
            }}></TextField>
        </Grid>
      </React.Fragment>
    );
  };

  const notificationInput = () => {
    const report = reportingEnabled();
    const notify = or(interaction.settings.notifications, pond.InteractionNotifications.create())
      .notify;
    return (
      <React.Fragment>
        {!isResultType(Result.report) && (
          <Grid size={{xs:6, sm:3}}>
            <FormControlLabel
              control={
                <Checkbox
                  id="notificationReports"
                  checked={reportingEnabled()}
                  onChange={toggleReport}
                  value="notificationReports"
                  color="secondary"
                />
              }
              label="Report"
              disabled={!canEdit}
            />
          </Grid>
        )}
        <Grid size={{xs: isResultType(Result.toggle) ? 6 : 12, sm: 3}} >
          <FormControlLabel
            control={
              <Checkbox
                id="interactionNotification"
                checked={notify}
                onChange={toggleNotify}
                value="interactionNotification"
                color="secondary"
              />
            }
            label="Notify"
            disabled={!canEdit || !report}
          />
        </Grid>
        {notify && (
          <Grid size={{ xs: 12, sm: 12 }}>
            <Typography color="textSecondary" variant="subtitle1">
              Everyone with notifications enabled for this device will receive an email and/or SMS
              each time this result occurs
            </Typography>
          </Grid>
        )}
      </React.Fragment>
    );
  };

  const sinkSelector = () => {
    return (
      <TextField
        select
        id="sink"
        label=""
        value={componentIDToString(interaction.settings.sink)}
        onChange={changeSink}
        fullWidth
        autoFocus={false}
        margin="dense"
        variant="standard"
        InputLabelProps={{ shrink: true }}
        disabled={isInitialComponentTheSink() || !canEdit || mode === "update"}>
        {sinkMenuItems()}
      </TextField>
    );
  };

  const setMenuItems = () => {
    let type = or(interaction.settings.sink, quack.ComponentID.create()).type;
    let states = extension(type).states.map((state, i) => (
      <MenuItem key={state} value={i}>
        {state}
      </MenuItem>
    ));
    return states;
  };

  const resultInput = () => {
    const sink = or(interaction.settings.sink, quack.ComponentID.create());
    const result = or(
      interaction.settings.result,
      pond.InteractionResult.create({
        type: Result.report
      })
    );
    const isRun = result.type === Result.run;
    const isMotor = sink.type === ComponentType.stepperMotor;
    return (
      <FormControl component={"fieldset" as "div"} className={classes.borderedContainer} fullWidth>
        <FormLabel component={"legend" as "caption"}>Result</FormLabel>
        <Grid container direction="row" spacing={2}>
          <Grid size={{ xs: 12, sm: 3 }}>
            <TextField
              select
              id="resultType"
              required={true}
              label=""
              value={result.type}
              onChange={changeResultType}
              autoFocus={false}
              margin="dense"
              variant="standard"
              fullWidth
              InputLabelProps={{ shrink: true }}
              disabled={!canEdit}>
              <MenuItem value={Result.report}>Report</MenuItem>
              <MenuItem value={Result.set}>Set</MenuItem>
              <MenuItem value={Result.toggle}>Toggle</MenuItem>
              <MenuItem value={Result.run}>Run</MenuItem>
            </TextField>
          </Grid>
          {isRun && (
            <React.Fragment>
              <Grid size={{ xs: 12, sm: isMotor ? 8 : 4 }}>
                {sinkSelector()}
              </Grid>
              {isMotor && (
                <Grid size={{ xs: 12, sm: 5 }} container justifyContent="center">
                  <TextField
                    select
                    id="mode"
                    label=""
                    value={result.mode}
                    onChange={changeResultMode}
                    fullWidth
                    autoFocus={false}
                    margin="dense"
                    variant="standard"
                    InputLabelProps={{ shrink: true }}
                    disabled={!canEdit}>
                    <MenuItem key={"Clockwise"} value={0}>
                      Clockwise
                    </MenuItem>
                    <MenuItem key={"Counter Clockwise"} value={1}>
                      Counter Clockwise
                    </MenuItem>
                  </TextField>
                </Grid>
              )}
              <Grid size={{ xs: 2, sm: 1 }}>
                <Typography variant="subtitle1" color="textSecondary" className={classes.forPrompt}>
                  for
                </Typography>
              </Grid>
              <Grid size={{ xs: 10, sm: isMotor ? 6 : 4 }} container justifyContent="center">
                <TextField
                  id="runDuration"
                  required={true}
                  label=""
                  helperText=""
                  error={invalidResultValue()}
                  value={rawResultValue}
                  onChange={changeResultValue}
                  autoFocus={false}
                  margin="dense"
                  variant="standard"
                  fullWidth
                  InputProps={{
                    endAdornment: <InputAdornment position="end">sec</InputAdornment>
                  }}
                  InputLabelProps={{ shrink: true }}
                  disabled={!canEdit}
                />
              </Grid>
            </React.Fragment>
          )}
          {isResultType(Result.set) && (
            <React.Fragment>
              <Grid size={{ xs: 12, sm: 4 }}>
                {sinkSelector()}
              </Grid>
              {interaction.settings.sink?.type !==
                quack.ComponentType.COMPONENT_TYPE_INTERNAL_FUNCTION && (
                <Grid size={{ xs: 2, sm: 1 }}>
                  <Typography
                    variant="subtitle1"
                    color="textSecondary"
                    className={classes.forPrompt}>
                    to
                  </Typography>
                </Grid>
              )}
              {interaction.settings.sink?.type !==
                quack.ComponentType.COMPONENT_TYPE_INTERNAL_FUNCTION && (
                <Grid size={{ xs: 10, sm: 3 }} container justifyContent="center">
                  <TextField
                    select
                    id="resultValue"
                    label=""
                    value={result.value}
                    onChange={changeResultValue}
                    fullWidth
                    autoFocus={false}
                    margin="dense"
                    variant="standard"
                    InputLabelProps={{ shrink: true }}
                    disabled={!canEdit}>
                    {setMenuItems()}
                  </TextField>
                </Grid>
              )}
            </React.Fragment>
          )}
          {isResultType(Result.toggle) && (
            <React.Fragment>
              <Grid size={{ xs: 12, sm: 5 }}>
                {sinkSelector()}
              </Grid>
              <Grid size={{ xs: 12, sm: 3 }} container justifyContent="center">
                <FormControlLabel
                  control={
                    <Switch
                      id="resultValue"
                      checked={result.value === 1}
                      onChange={changeResultValue}
                      value="toggleResultValue"
                      color="secondary"
                    />
                  }
                  label={describeSink().enumerations()[result.value]}
                  disabled={!canEdit}
                />
              </Grid>
            </React.Fragment>
          )}
          {hasDeviceFeature(device.settings.upgradeChannel, "better-controls") &&
            isRun &&
            dutyCycleInput()}
          {notificationInput()}
        </Grid>
      </FormControl>
    );
  };

  const daySelector = (day: string, size: any) => {
    const schedule = or(interaction.settings.schedule, pond.InteractionSchedule.create());
    return (
      <Grid container size={{ xs: size, sm: 1 }} justifyContent="center" alignItems="center">
        <FormControlLabel
          control={
            <Checkbox
              id={day}
              checked={schedule.weekdays.includes(day)}
              onChange={event => toggleDaySelected(day, event)}
              value={day}
              color="secondary"
            />
          }
          label={capitalize(day.substr(0, 2))}
          labelPlacement="bottom"
          disabled={!canEdit}
        />
      </Grid>
    );
  };

  const scheduleInput = () => {
    let schedule = or(interaction.settings.schedule, pond.InteractionSchedule.create());
    let timezone = coalesce(schedule.timezone, moment.tz.guess());
    let todStart = coalesce(schedule.timeOfDayStart, "00:00").split(":");
    let todEnd = coalesce(schedule.timeOfDayEnd, "23:59").split(":");
    let start = moment
      .tz(timezone)
      .startOf("day")
      .add(todStart[0], "hours")
      .add(todStart[1], "minutes")
      .local();
    let end = moment
      .tz(timezone)
      .startOf("day")
      .add(todEnd[0], "hours")
      .add(todEnd[1], "minutes")
      .local();
    let descriptor = timeOfDayDescriptor(schedule);
    let showStart = descriptor === "after" || descriptor === "from";
    let showEnd = descriptor === "before" || descriptor === "from";
    let showBoth = showStart && showEnd;
    return (
      <FormControl
        component={"fieldset" as "div"}
        className={classes.borderedContainer}
        fullWidth
        disabled={!canEdit}>
        <FormLabel component={"legend" as "caption"}>Schedule</FormLabel>
        <Grid container direction="row" spacing={0} justifyContent="flex-start">
          {daySelector("sunday", 3)}
          {daySelector("monday", 3)}
          {daySelector("tuesday", 3)}
          {daySelector("wednesday", 3)}
          {daySelector("thursday", 4)}
          {daySelector("friday", 4)}
          {daySelector("saturday", 4)}
        </Grid>
        <Grid container direction="row" spacing={2} alignItems="center">
          <Grid size={{ xs: 12, sm: 3 }}>
            <TextField
              select
              id="timeOfDayShortcut"
              value={descriptor}
              onChange={(event: any) => setScheduleTime("shortcut", event)}
              fullWidth
              autoFocus={false}
              margin="normal"
              variant="standard"
              InputLabelProps={{ shrink: true }}>
              <MenuItem key="allDay" value="allDay">
                All Day
              </MenuItem>
              <MenuItem key="before" value="before">
                Before
              </MenuItem>
              <MenuItem key="after" value="after">
                After
              </MenuItem>
              <MenuItem key="from" value="from">
                From
              </MenuItem>
            </TextField>
          </Grid>
          {showStart && (
            <Grid size={{ xs: 5, sm: 3 }}>
              <LocalizationProvider dateAdapter={AdapterMoment}>
                <TimePicker
                  value={start}
                  onChange={(event) => setScheduleTime("start", event)}
                  slotProps={{
                    textField: (props) => <TextField {...props} helperText="" />,
                  }}
                />
              </LocalizationProvider>
            </Grid>
          )}
          {showBoth && (
            <Grid size={{ xs: 2, sm: 1 }}>
              <Typography
                variant="subtitle1"
                color="textSecondary"
                className={classes.timeRange}
                align="right">
                to
              </Typography>
            </Grid>
          )}
          {showEnd && (
            <Grid size={{ xs: 5, sm: 3 }}>
              <LocalizationProvider dateAdapter={AdapterMoment}>
                <TimePicker
                  value={end}
                  onChange={(event: any) => setScheduleTime("end", event)}
                  slotProps={{
                    textField: (props) => <TextField {...props} helperText="" />,
                  }}
                />
              </LocalizationProvider>
            </Grid>
          )}
        </Grid>
      </FormControl>
    );
  };

  const content = () => {
    return (
      <Grid container direction="row" spacing={2}>
        <Grid size={{ xs: 12 }}>
          {sourceInput()}
        </Grid>
        {hasDeviceFeature(device.settings.upgradeChannel, "better-controls") && (
          <Grid size={{ xs: 12 }}>
            <div style={{ display: "flex", flexDirection: "row" }}>
              {priorityInput()}
              <div style={{ marginLeft: theme.spacing(2) }}></div>
              {sortingInput()}
            </div>
          </Grid>
        )}
        <Grid size={{ xs: 12 }}>
          {conditionInput()}
        </Grid>
        <Grid size={{ xs: 12 }}>
          {resultInput()}
        </Grid>
        <Grid size={{ xs: 12 }} >
          {scheduleInput()}
        </Grid>
      </Grid>
    );
  };

  const actions = () => {
    return (
      <Grid container justifyContent="space-between" direction="row" width={"100%"}>
        <Grid size={{ xs: 5 }}>
          {mode === "update" && canEdit && (
            <DeleteButton onClick={openRemoveInteraction}>Delete</DeleteButton>
          )}
        </Grid>
        <Grid size={{ xs: 7 }} container justifyContent="flex-end">
        <CancelSubmit onSubmit={submit} onCancel={close} submitDisabled={!canEdit || !isInteractionValid(interaction) || invalidConditionValues()}/>
        {/* <Button onClick={close} color="primary">
          Cancel
        </Button>
        {canEdit && (
          <Button
            onClick={submit}
            color="primary"
            disabled={!isInteractionValid(interaction) || invalidConditionValues()}>
            Submit
          </Button>
        )} */}
        </Grid>
      </Grid>
    );
  };

  return (
    <ResponsiveDialog
      fullWidth
      open={isDialogOpen}
      onClose={close}
      aria-labelledby="interaction-settings-dialog">
      <DialogTitle id="interaction-settings-title">{title()}</DialogTitle>
      <Divider />
      <DialogContent className={classes.dialogContent}>{content()}</DialogContent>
      <Divider />
      <DialogActions>{actions()}</DialogActions>
      {mode === "update" && (
        <RemoveInteraction
          device={device}
          interaction={interaction}
          isDialogOpen={isRemoveInteractionOpen}
          closeDialogCallback={closeRemoveInteraction}
          refreshCallback={interactionRemoved}
        />
      )}
    </ResponsiveDialog>
  );
}
