import {
  Background,
  Controls,
  Panel,
  ReactFlow,
  ReactFlowProvider,
  MarkerType,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import {type CSSProperties, useEffect, useState} from "react";
import {useShallow} from 'zustand/react/shallow';
import orderPhaseNodeArray from "../../utils/orderPhaseNodes.ts";
import useProgramStore from "../../utils/programStore.ts";
import {DndToolbar} from './visualProgrammingUI/components/DragDropSidebar.tsx';
import type {PhaseNode} from "./visualProgrammingUI/nodes/PhaseNode.tsx";
import useFlowStore from './visualProgrammingUI/VisProgStores.tsx';
import type {FlowState} from './visualProgrammingUI/VisProgTypes.tsx';
import styles from './VisProg.module.css'
import { NodeReduces, NodeTypes } from './visualProgrammingUI/NodeRegistry.ts';
import SaveLoadPanel from './visualProgrammingUI/components/SaveLoadPanel.tsx';

// --| config starting params for flow |--


/**
 * defines how the default edge looks inside the editor
 */
const DEFAULT_EDGE_OPTIONS = {
  type: 'default',
  markerEnd: {
    type: MarkerType.ArrowClosed,
    color: '#505050',
  },
};


/**
 * defines what functions in the FlowState store map to which names,
 * @param state
 */
const selector = (state: FlowState) => ({
  nodes: state.nodes,
  edges: state.edges,
  onNodesChange: state.onNodesChange,
  onEdgesDelete: state.onEdgesDelete,
  onEdgesChange: state.onEdgesChange,
  onConnect: state.onConnect,
  onReconnectStart: state.onReconnectStart,
  onReconnectEnd: state.onReconnectEnd,
  onReconnect: state.onReconnect,
  undo: state.undo,
  redo: state.redo,
  beginBatchAction: state.beginBatchAction,
  endBatchAction: state.endBatchAction,
  scrollable: state.scrollable
});

// --| define ReactFlow editor |--

/**
 * Defines the ReactFlow visual programming editor component
 * any implementations of editor logic should be encapsulated where possible
 * so the Component definition stays as readable as possible
 * @constructor
 */
const VisProgUI = () => {
  const {
    nodes, edges,
    onNodesChange,
    onEdgesDelete,
    onEdgesChange,
    onConnect,
    onReconnect,
    onReconnectStart,
    onReconnectEnd,
    undo,
    redo,
    beginBatchAction,
    endBatchAction,
    scrollable
  } = useFlowStore(useShallow(selector)); // instructs the editor to use the corresponding functions from the FlowStore
  const [zoom, setZoom] = useState(1);
  // adds ctrl+z and ctrl+y support to respectively undo and redo actions
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (e.ctrlKey && e.key === 'z') undo();
      if (e.ctrlKey && e.key === 'y') redo();
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  });

  return (
    <div className={`${styles.innerEditorContainer} round-lg border-lg`} style={({'--flow-zoom': zoom} as CSSProperties)}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        defaultEdgeOptions={DEFAULT_EDGE_OPTIONS}
        nodeTypes={NodeTypes}
        onNodesChange={onNodesChange}
        onEdgesDelete={onEdgesDelete}
        onEdgesChange={onEdgesChange}
        onReconnect={onReconnect}
        onReconnectStart={onReconnectStart}
        onReconnectEnd={onReconnectEnd}
        onConnect={onConnect}
        onNodeDragStart={beginBatchAction}
        onNodeDragStop={endBatchAction}
        preventScrolling={scrollable}
        onMove={(_, viewport) => setZoom(viewport.zoom)}
        snapToGrid
        fitView
        proOptions={{hideAttribution: true}}
      >
        <Panel position="top-center" className={styles.dndPanel}>
          <DndToolbar/> {/* contains the drag and drop panel for nodes */}
          </Panel>
          <Panel position = "bottom-left" className={styles.saveLoadPanel}>
            <SaveLoadPanel></SaveLoadPanel>
          </Panel>
        <Panel position="bottom-center">
          <button onClick={() => undo()}>undo</button>
          <button onClick={() => redo()}>Redo</button>
        </Panel>
        <Controls/>
        <Background/>
      </ReactFlow>
    </div>
  );
};

/**
 * Places the VisProgUI component inside a ReactFlowProvider
 *
 * Wrapping the editor component inside a ReactFlowProvider
 * allows us to access and interact with the components inside the editor, outside the editor definition,
 * thus facilitating the addition of node specific functions inside their node definitions
 */
function VisualProgrammingUI() {
  return (
    <ReactFlowProvider>
      <VisProgUI/>
    </ReactFlowProvider>
  );
}

// currently outputs the prepared program to the console
function runProgram() {
  const phases = graphReducer();
  const program = {phases}
  console.log(JSON.stringify(program, null, 2));
  fetch(
    "http://localhost:8000/program",
    {
      method: "POST",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify(program),
    }
  ).then((res) => {
    if (!res.ok) throw new Error("Failed communicating with the backend.")
    console.log("Successfully sent the program to the backend.");

    // store reduced program in global program store for further use in the UI
    // when the program was sent to the backend successfully:
    useProgramStore.getState().setProgramState(structuredClone(program));
  }).catch(() => console.log("Failed to send program to the backend."));
  console.log(program);
}

/**
 * Reduces the graph into its phases' information and recursively calls their reducing function
 */ 
function graphReducer() {
  const { nodes } = useFlowStore.getState();
  return orderPhaseNodeArray(nodes.filter((n) => n.type == 'phase') as PhaseNode [])
    .map((n) => {
      const reducer = NodeReduces['phase'];
      return reducer(n, nodes)
    }); 
}



/**
 * houses the entire page, so also UI elements
 * that are not a part of the Visual Programming UI
 * @constructor
 */
function VisProgPage() {
  return (
    <>
      <VisualProgrammingUI/>
      <button onClick={runProgram}>run program</button>
    </>
  )
}

export default VisProgPage
