import {
  Handle,
  type NodeProps,
  Position,
  type Connection,
  type Edge,
  type Node,
} from '@xyflow/react';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import useFlowStore from '../VisProgStores';
import { useState } from 'react';
import { RealtimeTextField, TextField } from '../../../../components/TextField';
import duplicateIndices from '../../../../utils/duplicateIndices';

/**
 * 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 triggerType - The type of trigger ("keywords" or a custom string).
 * @property triggers - The list of keyword triggers (if applicable).
 * @property hasReduce - Whether this node supports reduction logic.
 */
export type TriggerNodeData = {
  label: string;
  droppable: boolean;
  triggerType: "keywords" | string;
  triggers: Keyword[] | never;
  hasReduce: boolean;
};


export type TriggerNode = Node<TriggerNodeData>


/**
 * Determines whether a Trigger node can connect to another node or edge.
 *
 * @param connection - The connection or edge being attempted to connect towards.
 * @returns `true` if the connection is defined; otherwise, `false`.
 */
export function TriggerNodeCanConnect(connection: Connection | Edge): boolean {
  return (connection != undefined);
}

/**
 * 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} = useFlowStore();

  const setKeywords = (keywords: Keyword[]) => {
    updateNodeData(props.id, {...data, triggers: keywords});
  }

  return <>
    <Toolbar nodeId={props.id} allowDelete={true}/>
    <div className={`${styles.defaultNode} ${styles.nodeTrigger} flex-col gap-sm`}>
      {data.triggerType === "emotion" && (
        <div className={"flex-row gap-md"}>Emotion?</div>
      )}
      {data.triggerType === "keywords" && (
        <Keywords
          keywords={data.triggers}
          setKeywords={setKeywords}
        />
      )}
      <Handle type="source" position={Position.Right} id="TriggerSource"/>
    </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[]) {
  // Replace this for nodes functionality
  if (nodes.length <= -1) { 
    console.warn("Impossible nodes length in TriggerReduce") 
  } 
  const data = node.data as TriggerNodeData;
    return {
      label: data.label,
      list: data.triggers,
    }
}

/**
 * Handles logic that occurs when a connection is made involving a Trigger node.
 *
 * @param thisNode - The current Trigger node being connected.
 * @param otherNode - The other node involved in the connection.
 * @param isThisSource - Whether this node was the source of the connection.
 */
export function TriggerConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
  // Replace this for connection logic
  if (thisNode == undefined && otherNode == undefined && isThisSource == false) { 
    console.warn("Impossible node connection called in EndConnects")
  }
}

// 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;


/**
 * Renders an input element that allows users to add new keyword triggers.
 *
 * When the input is committed, the `addKeyword` callback is called with the new keyword.
 *
 * @param param0 - An object containing the `addKeyword` function.
 * @returns A React element(React.JSX.Element) providing an input for adding keywords.
 */
function KeywordAdder({ addKeyword }: { addKeyword: (keyword: string) => void }) {
  const [input, setInput] = useState("");

  const text_input_id = "keyword_adder_input";

  return <div className={"flex-row gap-md"}>
    <label htmlFor={text_input_id}>New Keyword:</label>
    <RealtimeTextField
      id={text_input_id}
      value={input}
      setValue={setInput}
      onCommit={() => {
        if (!input) return;
        addKeyword(input);
        setInput("");
      }}
      placeholder={"..."}
      className={"flex-1"}
    />
  </div>;
}

/**
 * Displays and manages a list of keyword triggers for a Trigger node.
 * Handles adding, editing, and removing keywords, as well as detecting duplicate entries.
 *
 * @param keywords - The current list of keyword triggers.
 * @param setKeywords - A callback to update the keyword list in the parent node.
 * @returns A React element(React.JSX.Element) for editing keyword triggers.
 */
function Keywords({
  keywords,
  setKeywords,
}: {
  keywords: Keyword[];
  setKeywords: (keywords: Keyword[]) => void;
}) {
  type Interpolatable = string | number | boolean | bigint | null | undefined;

  const inputElementId = (id: Interpolatable) => `keyword_${id}_input`;

  /** Indices of duplicates in the keyword array. */
  const [duplicates, setDuplicates] = useState<number[]>([]);

  function replace(id: string, value: string) {
    value = value.trim();
    const newKeywords = value === ""
      ? keywords.filter((kw) => kw.id != id)
      : keywords.map((kw) => kw.id === id ? {...kw, keyword: value} : kw);
    setKeywords(newKeywords);
    setDuplicates(duplicateIndices(newKeywords.map((kw) => kw.keyword)));
  }

  function add(value: string) {
    value = value.trim();
    if (value === "") return;
    const newKeywords = [...keywords, {id: crypto.randomUUID(), keyword: value}];
    setKeywords(newKeywords);
    setDuplicates(duplicateIndices(newKeywords.map((kw) => kw.keyword)));
  }

  return <>
    <span>Triggers when {keywords.length <= 1 ? "the keyword is" : "all keywords are"} spoken.</span>
    {[...keywords].map(({id, keyword}, index) => {
      return <div key={id} className={"flex-row gap-md"}>
        <label htmlFor={inputElementId(id)}>Keyword:</label>
        <TextField
          id={inputElementId(id)}
          value={keyword}
          setValue={(val) => replace(id, val)}
          placeholder={"..."}
          className={"flex-1"}
          invalid={duplicates.includes(index)}
        />
      </div>;
    })}
    <KeywordAdder addKeyword={add} />
  </>;
}