// This program has been developed by students from the bachelor Computer Science at Utrecht
// University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences)
import {getConnectedEdges, type Node, type NodeProps, Position, useNodeConnections} from '@xyflow/react';
import {useEffect, useState} from "react";
import styles from '../../VisProg.module.css';
import type {EditorWarning} from "../components/EditorWarnings.tsx";
import {Toolbar} from '../components/NodeComponents.tsx';
import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from "../VisProgStores.tsx";
import {BeliefGlobalReduce, noBeliefCycles, noMatchingLeftRightBelief} from "./BeliefGlobals.ts";
import switchStyles from './InferredBeliefNode.module.css';


/**
 * The default data structure for an InferredBelief node
 */
export type InferredBeliefNodeData = {
  label: string;
  droppable: boolean;
  inferredBelief: InferredBelief;
  hasReduce: boolean;
};

/**
 * stores a boolean to represent the operator
 * and a left and right BeliefNode (can be both an inferred and a basic belief)
 * in the form of their corresponding id's
 */
export type InferredBelief = {
  left: string | undefined,
  operator: boolean,
  right: string | undefined,
}

export type InferredBeliefNode = Node<InferredBeliefNodeData>;

/**
 * This function is called whenever a connection is made with this node type as the target
 * @param _thisNode the node of this node type which function is called
 * @param _sourceNodeId the source of the received connection
 */
export function InferredBeliefConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
  const data = _thisNode.data as InferredBeliefNodeData;

  if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId
    && ['basic_belief', 'inferred_belief'].includes(node.type!)))
  ) {
    const connectedEdges = getConnectedEdges([_thisNode], useFlowStore.getState().edges);
    switch(connectedEdges.find(edge => edge.source === _sourceNodeId)?.targetHandle){
      case 'beliefLeft': data.inferredBelief.left = _sourceNodeId; break;
      case 'beliefRight': data.inferredBelief.right = _sourceNodeId; break;
    }
  }
}

/**
 * This function is called whenever a connection is made with this node type as the source
 * @param _thisNode the node of this node type which function is called
 * @param _targetNodeId the target of the created connection
 */
export function InferredBeliefConnectionSource(_thisNode: Node, _targetNodeId: string) {
  // no additional connection logic exists yet
}

/**
 * This function is called whenever a connection is disconnected with this node type as the target
 * @param _thisNode the node of this node type which function is called
 * @param _sourceNodeId the source of the disconnected connection
 */
export function InferredBeliefDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
  const data = _thisNode.data as InferredBeliefNodeData;

  if (_sourceNodeId === data.inferredBelief.left) data.inferredBelief.left = undefined;
  if (_sourceNodeId === data.inferredBelief.right) data.inferredBelief.right = undefined;
}

/**
 * This function is called whenever a connection is disconnected with this node type as the source
 * @param _thisNode the node of this node type which function is called
 * @param _targetNodeId the target of the diconnected connection
 */
export function InferredBeliefDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
  // no additional connection logic exists yet
}

export const InferredBeliefTooltip = `
  Combines two beliefs into a single belief using logical inference, 
  the node can be toggled between using "AND" and "OR" mode for inference`;
/**
 * Defines how an InferredBelief node should be rendered
 * @param {NodeProps<InferredBeliefNode>} props - Node properties provided by React Flow, including `id` and `data`.
 * @returns The rendered InferredBeliefNode React element. (React.JSX.Element)
 */
export default function InferredBeliefNode(props: NodeProps<InferredBeliefNode>) {
  const data = props.data;
  const { updateNodeData, registerWarning, unregisterWarning } = useFlowStore();
  // start of as an AND operator, true: "AND", false: "OR"
  const [enforceAllBeliefs, setEnforceAllBeliefs] = useState(true);

  // used to toggle operator
  function onToggle() {
    const newOperator = !enforceAllBeliefs; // compute the new value
    setEnforceAllBeliefs(newOperator);

    updateNodeData(props.id, {
      ...data,
      inferredBelief: {
        ...data.inferredBelief,
        operator: enforceAllBeliefs,
      }
    });
  }

  const beliefConnections = useNodeConnections({
    id: props.id,
    handleType: "target",
  })

  useEffect(() => {
    const noBeliefsWarning : EditorWarning = {
      scope: {
        id: props.id,
        handleId: undefined
      },
      type: 'MISSING_INPUT',
      severity: 'ERROR',
      description: `This AND/OR node is missing one or more beliefs, 
      please make sure to use both inputs of an AND/OR node`
    };

    if (beliefConnections.length < 2){
      registerWarning(noBeliefsWarning);
      return;
    }
    unregisterWarning(props.id, noBeliefsWarning.type);
  },[beliefConnections.length, props.id, registerWarning, unregisterWarning])
  return (
    <>
      <Toolbar nodeId={props.id} allowDelete={true}/>
      <div className={`${styles.defaultNode} ${styles.nodeInferredBelief}`}>
        {/* The checkbox used to toggle the operator between 'AND' and 'OR' */}
        <label className={switchStyles.operatorSwitch}>
          <input
            type="checkbox"
            checked={data.inferredBelief.operator}
            onChange={onToggle}
          />
          <div className={switchStyles.switchVisual}></div>
          <div className={switchStyles.switchLabels}>
            <span title={"Belief is fulfilled if either of the supplied beliefs is true"}>OR</span>
            <span title={"Belief is fulfilled if all of the supplied beliefs are true"}>AND</span>
          </div>
        </label>


        {/* outgoing connections */}
        <MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[
          allowOnlyConnectionsFromType(["norm", "trigger"]),
          noBeliefCycles,
          noMatchingLeftRightBelief
        ]}/>

        {/* incoming connections */}
        <SingleConnectionHandle type="target" position={Position.Left} style={{top: '30%'}} id="beliefLeft" rules={[
          allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]),
          noBeliefCycles,
          noMatchingLeftRightBelief
        ]}/>
        <SingleConnectionHandle type="target" position={Position.Left} style={{top: '70%'}} id="beliefRight" rules={[
          allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]),
          noBeliefCycles,
          noMatchingLeftRightBelief
        ]}/>
      </div>
    </>
  );
};

/**
 * Reduces each BasicBelief, including its children down into its core data.
 * @param {Node} node - The BasicBelief node to reduce.
 * @param {Node[]} nodes - The list of all nodes in the current flow graph.
 * @returns A simplified object containing the node label and its list of BasicBeliefs.
 */
export function InferredBeliefReduce(node: Node, nodes: Node[]) {
  const data = node.data as InferredBeliefNodeData;
  const leftBelief = nodes.find((node) => node.id === data.inferredBelief.left);
  const rightBelief = nodes.find((node) => node.id === data.inferredBelief.right);

  if (!leftBelief) { throw new Error("No Left belief found")}
  if (!rightBelief) { throw new Error("No Right Belief found")}

  const result: Record<string, unknown> = {
    id: node.id,
    left: BeliefGlobalReduce(leftBelief, nodes),
    operator: data.inferredBelief.operator ? "AND" : "OR",
    right: BeliefGlobalReduce(rightBelief, nodes),
  };

  return result
}