// 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 Connection} from "@xyflow/react";
import {useEffect} from "react";
import useFlowStore from "./VisProgStores.tsx";

export type ConnectionContext = {
  connectionCount: number;
  source: {
    id: string;
    handleId: string;
  }
  target: {
    id: string;
    handleId: string;
  }
}

export type HandleRule = (
  connection: Connection,
  context: ConnectionContext
) => RuleResult;

/**
 * A RuleResult describes the outcome of validating a HandleRule
 *
 * if a rule is not satisfied, the RuleResult includes a message that is used inside a tooltip
 * that tells the user why their attempted connection is not possible
 */
export type RuleResult =
  | { isSatisfied: true }
  | { isSatisfied: false, message: string };

/**
 * default RuleResults, can be used to create more readable handleRule definitions
 */
export const ruleResult = {
  satisfied: { isSatisfied: true } as RuleResult,
  unknownError: {isSatisfied: false, message: "Unknown Error" } as RuleResult,
  notSatisfied: (message: string) : RuleResult => { return {isSatisfied: false, message: message } }
}


const evaluateRules = (
  rules: HandleRule[],
  connection: Connection,
  context: ConnectionContext
) : RuleResult => {
  // evaluate the rules and check if there is at least one unsatisfied rule
  const failedRule = rules
    .map(rule => rule(connection, context))
    .find(result => !result.isSatisfied);

  return failedRule ? ruleResult.notSatisfied(failedRule.message) : ruleResult.satisfied;
}


/**
 * !DOCUMENTATION NOT FINISHED!
 *
 * - The output is a single RuleResult, meaning we only show one error message.
 * Error messages are prioritised by listOrder; Thus, if multiple HandleRules evaluate to false,
 * we only send the error message of the first failed rule in the target's registered list of rules.
 *
 * @param {string} nodeId
 * @param {string} handleId
 * @param type
 * @param {HandleRule[]} rules
 * @returns {(c: Connection) => RuleResult} a function that validates an attempted connection
 */
export function useHandleRules(
  nodeId: string,
  handleId: string,
  type: "source" | "target",
  rules: HandleRule[],
) : (c: Connection) => RuleResult {
  const edges = useFlowStore.getState().edges;
  const registerRules = useFlowStore((state) => state.registerRules);


  useEffect(() => {
    registerRules(nodeId, handleId, rules);
    // the following eslint disable is required as it wants us to use all possible dependencies for the useEffect statement,
    // however this would result in an infinite loop because it would change one of its own dependencies
    // so we only use those dependencies that we don't change ourselves
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [handleId, nodeId, registerRules]);

  return (connection: Connection) => {
    // inside this function we consider the target to be the target of the isValidConnection event
    // and not the target in the actual connection
    const { target, targetHandle } = type === "source"
      ? connection
      : { target: connection.source, targetHandle: connection.sourceHandle };

    if (!targetHandle) {throw new Error("No target handle was provided");}

    const targetConnections = edges.filter(edge => edge.target === target && edge.targetHandle === targetHandle);


    // we construct the connectionContext
    const context: ConnectionContext = {
      connectionCount: targetConnections.length,
      source: {id: nodeId, handleId: handleId},
      target: {id: target, handleId: targetHandle},
    };
    const targetRules = useFlowStore.getState().getTargetRules(target, targetHandle);

    // finally we return a function that evaluates all rules using the created context
    return evaluateRules(targetRules, connection, context);
  };
}

export function validateConnectionWithRules(
  connection: Connection,
  context: ConnectionContext
): RuleResult {
  const rules = useFlowStore.getState().getTargetRules(
    connection.target!,
    connection.targetHandle!
  );

  return evaluateRules(rules,connection, context);
}