import {create} from 'zustand';
import {
  applyNodeChanges,
  applyEdgeChanges,
  addEdge,
  reconnectEdge, type Edge, type Connection
} from '@xyflow/react';

import {type AppNode, type FlowState} from './VisProgTypes.tsx';

/**
 * contains the nodes that are created when the editor is loaded,
 * should contain at least a start and an end node
 */
const initialNodes = [
  {
    id: 'start',
    type: 'start',
    position: {x: 0, y: 0},
    data: {label: 'start'}
  },
  {
    id: 'phase-1',
    type: 'phase',
    position: {x: 0, y: 150},
    data: {label: 'Generic Phase', number: 1},
  },
  {
    id: 'end',
    type: 'end',
    position: {x: 0, y: 300},
    data: {label: 'End'}
  }
];

/**
 * contains the initial edges that are created when the editor is loaded
 */
const initialEdges = [
  {
    id: 'start-phase-1',
    source: 'start',
    target: 'phase-1',
  },
  {
    id: 'phase-1-end',
    source: 'phase-1',
    target: 'end',
  }
];

/**
 * The useFlowStore hook contains the implementation for editor functionality and state
 * we can use this inside our editor component to access the current state
 * and use any implemented functionality
 */
const useFlowStore = create<FlowState>((set, get) => ({
  nodes: initialNodes,
  edges: initialEdges,
  edgeReconnectSuccessful: true,
  onNodesChange: (changes) => {
    set({
      nodes: applyNodeChanges(changes, get().nodes)
    });
  },
  onEdgesChange: (changes) => {
    set({
      edges: applyEdgeChanges(changes, get().edges)
    });
  },
  // handles connection of newly created edges
  onConnect: (connection) => {
    set({
      edges: addEdge(connection, get().edges)
    });
  },
  // handles attempted reconnections of a previously disconnected edge
  onReconnect: (oldEdge: Edge, newConnection: Connection) => {
    get().edgeReconnectSuccessful = true;
    set({
      edges: reconnectEdge(oldEdge, newConnection, get().edges)
    });
  },
  // Handles initiation of reconnection of edges that are manually disconnected from a node
  onReconnectStart: () => {
    set({
      edgeReconnectSuccessful: false
    });
  },
  // Drops the edge from the set of edges, removing it from the flow, if no successful reconnection occurred
  onReconnectEnd: (_: unknown, edge: { id: string; }) => {
    if (!get().edgeReconnectSuccessful) {
      set({
        edges: get().edges.filter((e) => e.id !== edge.id),
      });
    }
    set({
      edgeReconnectSuccessful: true
    });
  },
  deleteNode: (nodeId: string) => {
    set({
      nodes: get().nodes.filter((n) => n.id !== nodeId),
      edges: get().edges.filter((e) => e.source !== nodeId && e.target !== nodeId)
    });
  },
  setNodes: (nodes) => {
    set({nodes});
  },
  setEdges: (edges) => {
    set({edges});
  },
/**
 * handles updating the data component of a node,
 * if the provided data object contains entries that aren't present in the updated node's data component
 * those entries are added to the data component,
 * entries that do exist within the node's data component,
 * are simply updated to contain the new value
 *
 * the data object
 * @param {string} nodeId
 * @param {object} data
 */
  updateNodeData: (nodeId: string, data) => {
    set({
      nodes: get().nodes.map((node) : AppNode => {
        if (node.id === nodeId) {
          return {
            ...node,
            data: {
              ...node.data,
              ...data
            }
          };
        } else { return node; }
      })
    });
  }
  }),
);

export default useFlowStore;