import { Box, Button, Card, Typography } from "@mui/material";
import { useCallback, useEffect, useState } from "react";
import Alerts, { Alert } from "./Alerts";
import Controls, { Control } from "./Controls";
import { Component, Device, Interaction } from "models";
import { quack } from "protobuf-ts/quack";
import { Option } from "common/SearchSelect";
import { useInteractionsAPI } from "hooks";
import { useGlobalState } from "providers";
import { extension, getFriendlyName } from "pbHelpers/ComponentType";
import { sameComponentID } from "pbHelpers/Component";
import Notifications from "./Notifications";
import { pond } from "protobuf-ts/pond";
import React from "react";
import NewObjectInteraction from "./NewObjectInteraction";

interface Props {
  linkedComponents: Map<string, Component>; //the component key to the component object
  componentDevices: Map<string, number>;
  objectKey: string
  objectType: pond.ObjectType
  devices: Device[];
  permissions: pond.Permission[]
}

export default function ObjectInteractions(props: Props) {
  const { linkedComponents, componentDevices, devices, objectKey, objectType, permissions } = props
  const [{as}] = useGlobalState()
  //list of alerts, each alert contains a list of interactions with matching conditions
  const [alerts, setAlerts] = useState<Alert[]>([]);
  //list of interactions relating to a controller
  const [controls, setControls] = useState<Control[]>([]);
  const interactionsAPI = useInteractionsAPI();
  const [openNewInteraction, setOpenNewInteraction] = useState(false)
  const [allSinks, setAllSinks] = useState<Component[]>([]);
  // const [typeOptions, setTypeOptions] = useState<Option[]>([]);
  const [selectedDevice, setSelectedDevice] = useState<Device | undefined>(undefined)
  

  const matchConditions = (interaction: Interaction, set: Alert | Control) => {
    //if the number of conditions are not the same they are different
    if (interaction.settings.conditions.length !== set.conditions.length) {
      return false;
    }
    //continue with the comparison
    let matching = true;
    interaction.settings.conditions.forEach((condition, i) => {
      if (
        condition.comparison !== set.conditions[i].comparison ||
        condition.measurementType !== set.conditions[i].measurementType ||
        condition.value !== set.conditions[i].value
      ) {
        matching = false;
      }
    });
    return matching;
  };

  const matchResult = (interaction: Interaction, set: Control) => {
    let interactionResult = interaction.settings.result
    let setResult = set.result
    let matching = true
    // if anything in the result for the interaction is different from the set make matching false 
    if(interactionResult?.type !== setResult?.type ||
       interactionResult?.mode !== setResult?.mode ||
       interactionResult?.value !== setResult?.value ||
       interactionResult?.dutyCycle !== setResult?.dutyCycle
    ){
      matching = false
    }
    return matching
  }

  const findDevice = (deviceID: number) => {
    for (let device of devices) {
      if (device.id() === deviceID) return device
    }
  }

  const findSinkComponent = (sinks: Component[], interactionSink: quack.ComponentID, sourceDevice: number) => {
    for (let component of sinks) {
      if (sameComponentID(component.location(), interactionSink) && sourceDevice === componentDevices.get(component.key())){
        return component
      }
    }
  }

  const load = useCallback(()=>{
const newInteractions: Interaction[] = [];
    const interactionSourceMap = new Map<string, string>();
    const sinkOptions: Component[] = [];
    const alerts: Alert[] = [];
    const controls: Control[] = [];
  
    const run = async () => {
      // Run all component API fetches in parallel
      await Promise.all(
        [...linkedComponents].map(async ([key, comp]) => {
          const device = componentDevices.get(key);
  
          if (device) {
            const resp = await interactionsAPI.listInteractionsByComponent(
              device,
              comp.location(),
              undefined,
              as
            );
  
            resp.forEach(interaction => interactionSourceMap.set(interaction.key(), key));
            newInteractions.push(...resp);
          }
  
          // Collect controller components
          if (extension(comp.type()).isController) {
            sinkOptions.push(comp);
          }
        })
      );
    };
  
    run().then(() => {
      newInteractions.forEach(interaction => {
        //alert and control are not mutally exclusive, it is possible to be both if the control is sending notifications
        if (interaction.isAlert()) {
          // Find matching alert
          let similarAlertIndex = alerts.findIndex(alert =>
            matchConditions(interaction, alert)
          );
      
          const compKey = interactionSourceMap.get(interaction.key());
          const component = compKey ? linkedComponents.get(compKey) : undefined;
      
          if (component) {
            if (similarAlertIndex === -1) {
              alerts.push({
                conditions: interaction.settings.conditions,
                sourceComponents: [component],
              });
            } else {
              alerts[similarAlertIndex].sourceComponents.push(component);
            }
          }
      
        }
        if (interaction.isControl()) {
          if (!interaction.settings.sink) return //if there is no sink it cant be a control so move on to the next
          const compKey = interactionSourceMap.get(interaction.key());
          if (!compKey) return //we have to have a valid component key
          const component = linkedComponents.get(compKey);
          const deviceID = componentDevices.get(compKey);
          if (!deviceID) return //we have to know which device it is for
          const controlDevice = findDevice(deviceID)
          if (!controlDevice) return //failed to get the device from the array
          const sinkComponent = findSinkComponent(sinkOptions, interaction.settings.sink, deviceID)
          if (!sinkComponent) return //failed to get the correct sink component

          let similarControlIndex = controls.findIndex(control =>
            matchConditions(interaction, control) &&
            matchResult(interaction, control) &&
            sameComponentID(interaction.settings.sink, control.sinkComponent.location()) && //if the sink for the interaction is the same address as the existing control
            deviceID === control.device.id() //if the device id for source component is the same as the device id in the control
          );
      
          if (component && interaction.settings.result) {
            if (similarControlIndex === -1) {
              controls.push({
                conditions: interaction.settings.conditions,
                result: interaction.settings.result,
                sourceComponents: [component],
                sinkComponent: sinkComponent,
                device: controlDevice
              });
            } else {
              controls[similarControlIndex].sourceComponents.push(component);
            }
          }
        }
      });
      setAllSinks(sinkOptions);
      setAlerts(alerts);
      setControls(controls);
    }).catch(err => {
      console.error("Interaction fetch error:", err)
    }) 
  },[linkedComponents, interactionsAPI, componentDevices, as])

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



  return (
    <React.Fragment>
      <NewObjectInteraction 
        open={openNewInteraction} 
        onClose={(refresh) => {
          setOpenNewInteraction(false)
          if(refresh){
            load()
          }
        }}
        linkedComponents={linkedComponents}
        componentDevices={componentDevices}
        device={selectedDevice}/>
      <Card raised style={{ margin: 5 }}>
        <Box padding={1}>
          <Typography style={{ fontWeight: 650, fontSize: 25 }}>Interactions</Typography>
          <Controls 
            controls={controls} 
            permissions={permissions}
            devices={devices} 
            addNew={(device) => {
              setSelectedDevice(device)
              setOpenNewInteraction(true)
            }}/>
          <Alerts 
            alerts={alerts} 
            permissions={permissions}
            addNew={() => {
            setSelectedDevice(undefined)
            setOpenNewInteraction(true)
            }}/>
          <Notifications objectKey={objectKey} objectType={objectType}/>
        </Box>
      </Card>
    </React.Fragment>
  )
}