// 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 {
  type NodeProps,
  Position,
  type Node, useNodeConnections
} from '@xyflow/react';
import {useEffect} from "react";
import type {EditorWarning} from "../components/EditorWarnings.tsx";
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores';
import {PlanReduce, type Plan } from '../components/Plan';
import PlanEditorDialog from '../components/PlanEditor';
import {BeliefGlobalReduce} from "./BeliefGlobals.ts";
import type { GoalNode } from './GoalNode.tsx';
import { defaultPlan } from '../components/Plan.default.ts';
import { deleteGoalInPlanByID, insertGoalInPlan } from '../components/PlanEditingFunctions.tsx';
import { TextField } from '../../../../components/TextField.tsx';

/**
 * The default data structure for a Trigger node
 *
 * Represents configuration for a node that activates when a specific condition is met,
 * such as keywords being spoken or emotions detected.
 *
 * @property label: the display label of this Trigger node.
 * @property droppable: Whether this node can be dropped from the toolbar (default: true).
 * @property hasReduce - Whether this node supports reduction logic.
 */
export type TriggerNodeData = {
  label: string;
  name: string;
  droppable: boolean;
  condition?: string; // id of the belief
  plan?: Plan;
  hasReduce: boolean;
};


export type TriggerNode = Node<TriggerNodeData>

/**
 * Defines how a Trigger node should be rendered
 * @param props - Node properties provided by React Flow, including `id` and `data`.
 * @returns The rendered TriggerNode React element (React.JSX.Element).
 */
export default function TriggerNode(props: NodeProps<TriggerNode>) {
  const data = props.data;
  const {updateNodeData, registerWarning, unregisterWarning} = useFlowStore();

  const setName= (value: string) => {
    updateNodeData(props.id, {...data, name: value})
  }

  const beliefInput = useNodeConnections({
    id: props.id,
    handleType: "target",
    handleId: "TriggerBeliefs"
  })

  const outputCons = useNodeConnections({
    id: props.id,
    handleType: "source",
    handleId: "TriggerSource"
  })

  useEffect(() => {
    const noPhaseConnectionWarning : EditorWarning = {
      scope: {
        id: props.id,
        handleId: 'TriggerSource'
      },
      type: 'MISSING_OUTPUT',
      severity: 'INFO',
      description: "This triggerNode is missing a condition/belief, please make sure to connect a belief node to "
    };

    if (outputCons.length === 0){
      registerWarning(noPhaseConnectionWarning);
      return;
    }
    unregisterWarning(props.id, `${noPhaseConnectionWarning.type}:${noPhaseConnectionWarning.scope.handleId}`);
  },[outputCons.length, props.id, registerWarning, unregisterWarning])

  useEffect(() => {
    const noBeliefWarning : EditorWarning = {
      scope: {
        id: props.id,
        handleId: 'TriggerBeliefs'
      },
      type: 'MISSING_INPUT',
      severity: 'ERROR',
      description: "This triggerNode is missing a condition/belief, please make sure to connect a belief node to "
    };

    if (beliefInput.length === 0 && outputCons.length !== 0){
      registerWarning(noBeliefWarning);
      return;
    }
    unregisterWarning(props.id, `${noBeliefWarning.type}:${noBeliefWarning.scope.handleId}`);
  },[beliefInput.length, outputCons.length, props.id, registerWarning, unregisterWarning])

  useEffect(() => {
    const noPlanWarning : EditorWarning = {
      scope: {
        id: props.id,
        handleId: undefined
      },
      type: 'PLAN_IS_UNDEFINED',
      severity: 'ERROR',
      description: "This triggerNode is missing a plan, please make sure to create a plan by using the create plan button"
    };

    if ((!data.plan || data.plan.steps?.length === 0) && outputCons.length !== 0){
      registerWarning(noPlanWarning);
      return;
    }
    unregisterWarning(props.id, noPlanWarning.type);
  },[data.plan, outputCons.length, props.id, registerWarning, unregisterWarning])

  useEffect(() => {
  const name = data.name || "";
  
  if (/^\d/.test(name)) {
    registerWarning({
      scope: { id: props.id },
      type: 'ELEMENT_STARTS_WITH_NUMBER',
      severity: 'ERROR',
      description: "Trigger names are not allowed to start with a number."
    });
  } else {
    unregisterWarning(props.id, 'ELEMENT_STARTS_WITH_NUMBER');
  }
}, [data.name, props.id, registerWarning, unregisterWarning]);
  return <>

    <Toolbar nodeId={props.id} allowDelete={true}/>
    <div className={`${styles.defaultNode} ${styles.nodeTrigger} flex-col gap-sm`}>
      <TextField
                value={props.data.name}
                setValue={(val) => setName(val)}
                placeholder={"Name of this trigger..."}
      />
      <div className={"flex-row gap-md"}>Triggers when the condition is met.</div>
      <div className={"flex-row gap-md"}>Condition/ Belief is currently {data.condition ? "" : "not"} set. {data.condition ? "🟢" : "🔴"}</div>
      <div className={"flex-row gap-md"}>Plan{data.plan ? (": " + data.plan.name) : ""} is currently {data.plan ? "" : "not"} set. {data.plan ? "🟢" : "🔴"}</div>
      <MultiConnectionHandle type="source" position={Position.Right} id="TriggerSource" rules={[
        allowOnlyConnectionsFromHandle([{nodeType:"phase",handleId:"data"}]),
      ]} title="Connect to any number of phaseNodes"/>
      <SingleConnectionHandle
        type="target"
        position={Position.Bottom}
        id="TriggerBeliefs"
        style={{ left: '40%' }}
        rules={[
          allowOnlyConnectionsFromType(['basic_belief','inferred_belief']),
        ]}
        title="Connect to a beliefNode or a set of beliefs combined using the AND/OR node"
      />
      
      <MultiConnectionHandle
        type="target"
        position={Position.Bottom}
        id="GoalTarget"
        style={{ left: '60%' }}
        rules={[
          allowOnlyConnectionsFromType(['goal']),
        ]}
        title="Connect to any number of goalNodes"
      />

      <PlanEditorDialog
          plan={data.plan}
          onSave={(plan) => {
            updateNodeData(props.id, {
              ...data,
              plan,
            });
          }}
      />
    </div>
  </>;
}

/**
 * Reduces each Trigger, including its children down into its core data.
 * @param node - The Trigger node to reduce.
 * @param nodes - The list of all nodes in the current flow graph.
 * @returns A simplified object containing the node label and its list of triggers.
 */
export function TriggerReduce(node: Node, nodes: Node[]) {
  const data = node.data as TriggerNodeData;
  const conditionNode = data.condition ? nodes.find((n)=>n.id===data.condition) : undefined
  const conditionData = conditionNode ? BeliefGlobalReduce(conditionNode, nodes) : ""
  return {
    id: node.id,
    name: node.data.name,
    condition: conditionData, // Make sure we have a condition before reducing, or default to ""
    plan: !data.plan ? "" : PlanReduce(nodes, data.plan), // Make sure we have a plan when reducing, or default to ""
  }
  
}


export const TriggerTooltip = `
  A trigger node is used to make Pepper execute a predefined plan -
  consisting of one or more actions - when the connected beliefs are met`;

/**
 * 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 TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
  // no additional connection logic exists yet
  const data = _thisNode.data as TriggerNodeData;
  // If we got a belief connected, this is the condition for the norm.
  const nodes = useFlowStore.getState().nodes;
  const otherNode = nodes.find((x) => x.id === _sourceNodeId)
  if (!otherNode) return;

  if (['basic_belief', 'inferred_belief'].includes(otherNode.type!)) {
      data.condition = _sourceNodeId;
  }

  else if (otherNode.type === 'goal') {
      // First, let's see if we have a plan currently. If not, let's create a default plan with this goal inside.:)
    if (!data.plan) {
      data.plan = insertGoalInPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()} as Plan, otherNode as GoalNode)
    }

    // Else, lets just insert this goal into our current plan.
    else {
      data.plan = insertGoalInPlan(structuredClone(data.plan), otherNode as GoalNode)
    }
  }
}

/**
 * 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 TriggerConnectionSource(_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 TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
  // no additional connection logic exists yet
  const data = _thisNode.data as TriggerNodeData;
    // remove if the target of disconnection was our condition
    if (_sourceNodeId == data.condition) data.condition = undefined

  data.plan = deleteGoalInPlanByID(structuredClone(data.plan) as Plan, _sourceNodeId)
}

/**
 * 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 TriggerDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
  // no additional connection logic exists yet
}

// Definitions for the possible triggers, being keywords and emotions

/** Represents a single keyword trigger entry. */
type Keyword = { id: string, keyword: string };

/** Properties for an emotion-type trigger node. */
export type EmotionTriggerNodeProps = {
  type: "emotion";
  value: string;
}

/** Props for a keyword-type trigger node. */
export type KeywordTriggerNodeProps = {
  type: "keywords";
  value: Keyword[];
}

/** Union type for all possible Trigger node configurations. */
export type TriggerNodeProps = EmotionTriggerNodeProps | KeywordTriggerNodeProps;