// 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 {
  Background,
  Controls,
  Panel,
  ReactFlow,
  ReactFlowProvider,
  MarkerType, getOutgoers
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import warningStyles from './visualProgrammingUI/components/WarningSidebar.module.css'
import {type CSSProperties, useEffect, useState} from "react";
import {useShallow} from 'zustand/react/shallow';
import useProgramStore from "../../utils/programStore.ts";
import {DndToolbar} from './visualProgrammingUI/components/DragDropSidebar.tsx';
import {type EditorWarning, globalWarning} from "./visualProgrammingUI/components/EditorWarnings.tsx";
import {WarningsSidebar} from "./visualProgrammingUI/components/WarningSidebar.tsx";
import useFlowStore from './visualProgrammingUI/VisProgStores.tsx';
import type {FlowState} from './visualProgrammingUI/VisProgTypes.tsx';
import styles from './VisProg.module.css'
import {NodeTypes} from './visualProgrammingUI/NodeRegistry.ts';
import SaveLoadPanel from './visualProgrammingUI/components/SaveLoadPanel.tsx';
import MonitoringPage from '../MonitoringPage/MonitoringPage.tsx';
import {graphReducer, runProgram} from './VisProgLogic.ts';

// --| 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,
  onNodesDelete: state.onNodesDelete,
  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,
    onNodesDelete,
    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);
  });
  const {unregisterWarning, registerWarning} = useFlowStore();
  useEffect(() => {

    if (checkPhaseChain()) {
      unregisterWarning(globalWarning,'INCOMPLETE_PROGRAM');
    } else {
      // create global warning for incomplete program chain
      const incompleteProgramWarning : EditorWarning = {
        scope: {
          id: globalWarning,
          handleId: undefined
        },
        type: 'INCOMPLETE_PROGRAM',
        severity: "ERROR",
        description: "there is no complete phase chain from the startNode to the EndNode"
      }

      registerWarning(incompleteProgramWarning);
    }
  },[edges, registerWarning, unregisterWarning])

  return (
    <div className={`${styles.innerEditorContainer} round-lg border-lg flex-row`} style={({'--flow-zoom': zoom} as CSSProperties)}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        defaultEdgeOptions={DEFAULT_EDGE_OPTIONS}
        nodeTypes={NodeTypes}
        onNodesChange={onNodesChange}
        onNodesDelete={onNodesDelete}
        onEdgesDelete={onEdgesDelete}
        onEdgesChange={onEdgesChange}
        onReconnect={onReconnect}
        onReconnectStart={onReconnectStart}
        onReconnectEnd={onReconnectEnd}
        onConnect={onConnect}
        onNodeDragStart={beginBatchAction}
        onNodeDragStop={endBatchAction}
        preventScrolling={scrollable}
        onMove={(_, viewport) => setZoom(viewport.zoom)}
        reconnectRadius={15}
        snapToGrid
        fitView
        proOptions={{hideAttribution: true}}
        style={{flexGrow: 3}}
      >
        <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>
        <Panel position="center-right" className={warningStyles.warningsSidebar}>
          <WarningsSidebar/>
        </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>
  );
}

const checkPhaseChain = (): boolean => {
  const {nodes, edges} = useFlowStore.getState();

  function checkForCompleteChain(currentNodeId: string): boolean {
    const outgoingPhases = getOutgoers({id: currentNodeId}, nodes, edges)
      .filter(node => ["end", "phase"].includes(node.type!));

    if (outgoingPhases.length === 0) return false;
    if (outgoingPhases.some(node => node.type === "end" )) return true;

    const next = outgoingPhases.map(node => checkForCompleteChain(node.id))
      .find(result => result);
    return !!next;
  }

  return checkForCompleteChain('start');
};

/**
 * houses the entire page, so also UI elements
 * that are not a part of the Visual Programming UI
 * @constructor
 */
function VisProgPage() {
  const [showSimpleProgram, setShowSimpleProgram] = useState(false);
  const [programValidity, setProgramValidity] = useState<boolean>(true);
  const {isProgramValid, severityIndex} = useFlowStore();
  const setProgramState = useProgramStore((state) => state.setProgramState);

  const validity = () => {return isProgramValid();}

  useEffect(() => {
    setProgramValidity(validity);
    // the following eslint disable is required as it wants us to use all possible dependencies for the useEffect statement,
    // however this would cause unneeded updates
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [severityIndex]);

  const processProgram = () => {
    const phases = graphReducer();       // reduce graph
    setProgramState({ phases });          // <-- save to store
    setShowSimpleProgram(true);          // show SimpleProgram
    runProgram();                        // send to backend if needed
  };

  if (showSimpleProgram) {
    return (
    <div>
      <button className={styles.backButton} onClick={() => setShowSimpleProgram(false)}>
        Back to Editor ◀
      </button>
      <MonitoringPage/>
    </div>
    );
  }



  return (
    <>
      <VisualProgrammingUI/>
      <button onClick={processProgram} disabled={!programValidity}>Run Program</button>
    </>
  )
}

export default VisProgPage
