import { LinearProgress } from "@mui/material";
import { Bin, Component, Device, Interaction } from "models";
import Controls, { Control } from "objects/objectInteractions/Controls";
import NewObjectInteraction from "objects/objectInteractions/NewObjectInteraction";
import { sameComponentID } from "pbHelpers/Component";
import { extension } from "pbHelpers/ComponentType";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useGlobalState } from "providers";
import interactionsAPI, { useInteractionsAPI } from "providers/pond/interactionsAPI";
import React, { useCallback, useEffect, useState } from "react";

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

export default function BinControls(props: Props) {
    const {linkedComponents, componentDevices, devices, permissions} = props
    const [{as}] = useGlobalState()
    const interactionsAPI = useInteractionsAPI()
    const [controls, setControls] = useState<Control[]>([]);
    const [selectedDevice, setSelectedDevice] = useState<Device | undefined>(undefined)
    const [openNewInteraction, setOpenNewInteraction] = useState(false)
    const [loading, setLoading] = useState(false)


    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 matchConditions = (interaction: Interaction, set: 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 load = useCallback(()=>{
        if(!loading){
          setLoading(true)
          const newInteractions: Interaction[] = [];
          const interactionSourceMap = new Map<string, string>();
          const sinkOptions: Component[] = [];
          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 => {
              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);
                  }
                }
              }
            });
            setControls(controls);
          }).catch(err => {
            console.error("Interaction fetch error:", err)
          }).finally(() => {
            setLoading(false)
          })
        }
      },[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}/>
            {loading ? 
            <LinearProgress /> 
            :
            <Controls 
            controls={controls}
            devices={devices}
            permissions={permissions}
            addNew={(device) => {
                console.log("new control")
                setSelectedDevice(device)
                setOpenNewInteraction(true)
            }}
            />
        }
        </React.Fragment>
    )
}