/* eslint-disable react/no-multi-comp */
import React, { useState, useEffect, useLayoutEffect, memo, useMemo, useRef, useContext } from "react";
import ReactDOM from "react-dom"

import { getTheme } from "../theme.jsx";
import { useInterval } from "react-powerhooks";
import { makeStyles, } from "@mui/styles";

import YAML from "yaml";
import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid";
import { useNavigate, Link, useParams } from "react-router-dom";
import { useBeforeunload } from "react-beforeunload"
import ReactJson from "react-json-view-ssr";
import { NestedMenuItem } from 'mui-nested-menu';
import Markdown from "react-markdown";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import { isMobile } from "react-device-detect"
import aa from 'search-insights'
import Drift from "react-driftjs";
import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx";

import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom';
import algoliasearch from 'algoliasearch/lite';
import useDebouncedCallback from "../utils/useDebouncedCallback.jsx";
import {
  Zoom,
  Fade,
  Slide,

  Avatar,
  Popover,
  TextField,
  Drawer,
  Button,
  Paper,
  Grid,
  Tabs,
  InputAdornment,
  Tab,
  ButtonBase,
  Tooltip,
  Select,
  MenuItem,
  Divider,
  Dialog,
  DialogActions,
  DialogTitle,
  InputLabel,
  DialogContent,
  FormControl,
  IconButton,
  Menu,
  Input,
  FormGroup,
  FormControlLabel,
  Typography,
  Checkbox,
  Breadcrumbs,
  CircularProgress,
  SwipeableDrawer,
  Switch,
  Chip,
  Card,
  List,
  ListItem,
  ListItemText,
  ListItemAvatar,
  Badge,
  AvatarGroup,
  Autocomplete,
  Radio,
  ButtonGroup,
  Box,
  ToggleButton,
  ToggleButtonGroup,
} from "@mui/material";


import {
  Storage as StorageIcon, 
  Code as CodeIcon, 
  Folder as FolderIcon,
  VerifiedUser as VerifiedUserIcon,
  CheckCircle as CheckCircleIcon,
  Insights as InsightsIcon,
  LibraryBooks as LibraryBooksIcon,
  OpenInNew as OpenInNewIcon,
  Undo as UndoIcon,
  GetApp as GetAppIcon,
  Search as SearchIcon,
  Visibility as VisibilityIcon,
  Done as DoneIcon,
  Close as CloseIcon,
  DragIndicator as DragIndicatorIcon,
  Error as ErrorIcon,
  Warning as WarningIcon,
  ArrowLeft as ArrowLeftIcon,
  ArrowRight as ArrowRightIcon,
  Cached as CachedIcon,
  DirectionsRun as DirectionsRunIcon,
  FormatListNumbered as FormatListNumberedIcon,
  PlayArrow as PlayArrowIcon,
  AspectRatio as AspectRatioIcon,
  MoreVert as MoreVertIcon,
  Apps as AppsIcon,
  Schedule as ScheduleIcon,
  FavoriteBorder as FavoriteBorderIcon,
  Pause as PauseIcon,
  Delete as DeleteIcon,
  AddCircleOutline as AddCircleOutlineIcon,
  KeyboardArrowDown as KeyboardArrowDownIcon, 
  Save as SaveIcon,
  KeyboardArrowRight as KeyboardArrowRightIcon,
  ArrowBack as ArrowBackIcon,
  Settings as SettingsIcon,
  LockOpen as LockOpenIcon,
  ExpandMore as ExpandMoreIcon,
  VpnKey as VpnKeyIcon,
  AddComment as AddCommentIcon,
  Edit as EditIcon,
  Send as SendIcon,
  Restore as RestoreIcon,
  Preview as PreviewIcon,
  ContentCopy as ContentCopyIcon,
  Circle as CircleIcon,
  SquareFoot as SquareFootIcon,
  AutoFixHigh as AutoFixHighIcon,
  Polyline as PolylineIcon,
  QueryStats as QueryStatsIcon,
  AutoAwesome as AutoAwesomeIcon,

  Add as AddIcon,
  ErrorOutline as ErrorOutlineIcon,

  ArrowForward as ArrowForwardIcon,
  OpenInFull as OpenInFullIcon,
  Difference as DifferenceIcon,
  DataObject as DataObjectIcon, 
  SwapHoriz as SwapHorizIcon
} from "@mui/icons-material";

import cytoscape from "cytoscape";
import edgehandles from "cytoscape-edgehandles";

import CytoscapeComponent from "react-cytoscapejs";

import Draggable from "react-draggable";
import defaultCytoscapeStyle from "../defaultCytoscapeStyle.jsx";
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
import LineChartWrapper from "../components/LineChartWrapper.jsx";

import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
import { validateJson, collapseField, GetIconInfo, buildIconSvgPath, handleReactJsonClipboard, HandleJsonCopy, } from "../views/Workflows2.jsx";
import { GetParsedPaths, internalIds, } from "../views/Apps.jsx";
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
import ParsedAction from "../components/ParsedActionNew.jsx";
import PaperComponent from "../components/PaperComponent.jsx"
import ExtraApps from "../components/ExtraApps.jsx"
import EditWorkflow from "../components/EditWorkflow.jsx"
import HighlightedValueInSearch from "../components/HighlightedValueInSearch.jsx"
import { act } from "react";
import { Context } from "../context/ContextApi.jsx";
import WorkflowGenerationModal from "../components/WorkflowGenerationModal.jsx";
import CodeMirror from '@uiw/react-codemirror';
import { python } from '@codemirror/lang-python';
import { vscodeDark } from '@uiw/codemirror-theme-vscode';

cytoscape.use(edgehandles);

export const triggers = [
  {
    name: "Webhook",
    type: "TRIGGER",
    parameters : [
      {
          "name": "url",
          "example": "",
          "value": ""
      },
      {
          "name": "tmp",
          "example": "",
          "value": "",
      },
      {
        "name": "auth_headers",
        "example": "",
        "value": "",
      },
      {
        "name": "custom_response_body",
        "example": "",
        "value": "",
      },
      {
        "name": "await_response",
        "example": "",
        "value": "v1",
      },
    ],
    status: "uninitialized",
    trigger_type: "WEBHOOK",
    errors: null,
    large_image:
      "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4IiByeD0iOCIgZmlsbD0iIzIxQTBCRCIvPgo8cGF0aCBkPSJNMjMuOTYxNyAxOC4wOTk0QzI1LjAwMDQgMTguMDY5NiAyNS42ODE3IDE5LjE3NTIgMjUuMTg4MiAyMC4wODk2TDI1LjE3NDYgMjAuMTE1TDI1LjE5MDIgMjAuMTM5NEwyNy45ODMyIDI0LjY4MDRMMjguMDA0NiAyNC43MTU2TDI4LjA0MzcgMjQuN0MyOC41MzgzIDI0LjUwMDcgMjkuMDY3MSAyNC4zOTgyIDI5LjYwMDMgMjQuMzk4MkMzMS4wODE0IDI0LjM5ODkgMzIuNDQ5OSAyNS4xODkyIDMzLjE5MTIgMjYuNDcxNEMzNC43ODk2IDI5LjIzNzMgMzIuNzk0OCAzMi42OTY1IDI5LjYwMDMgMzIuNjk4QzI5LjM2ODEgMzIuNjk4IDI5LjE1MzkgMzIuNTczOSAyOS4wMzc4IDMyLjM3MjhDMjguNzg3NyAzMS45Mzk1IDI5LjEgMzEuMzk4MiAyOS42MDAzIDMxLjM5ODJDMzEuMjA2NyAzMS4zOTg4IDMyLjQ5NjEgMzAuMDcyMyAzMi40NSAyOC40NjY2QzMyLjM4NjcgMjYuMjczNiAyOS45NzM3IDI0Ljk3MTYgMjguMTA2MiAyNi4xMjI4QzI3LjgwMDggMjYuMzEwOCAyNy40MDAxIDI2LjIxNSAyNy4yMTE3IDI1LjkwOTlIMjcuMjEyNkwyNC4wODE4IDIwLjgyMkwyNC4wNjcxIDIwLjc5ODZIMjQuMDAwN0MyMy41MzIzIDIwLjc5ODYgMjMuMDk3MSAyMC41NTU4IDIyLjg1MTMgMjAuMTU3QzIyLjMwNjIgMTkuMjcyMyAyMi45MjMgMTguMTI5MyAyMy45NjE3IDE4LjA5OTRaTTE1LjA4MDggMjYuMDU3NEMxNS4zODE0IDI1LjY1NzIgMTYuMDAyNiAyNS43MzI4IDE2LjE5OSAyNi4xOTMxQzE2LjI3ODcgMjYuMzgwMSAxNi4yNjU3IDI2LjU5MTkgMTYuMTY3NyAyNi43NjY0TDE2LjEyMDggMjYuODM4NkMxNS43NDkxIDI3LjMzMSAxNS41NDg1IDI3LjkzMTYgMTUuNTUwNSAyOC41NDg2QzE1LjU1MDcgMzAuNzQyMyAxNy45MjYxIDMyLjExMzIgMTkuODI1OSAzMS4wMTY0QzIwLjcwNzUgMzAuNTA3MyAyMS4yNTA2IDI5LjU2NjYgMjEuMjUwNyAyOC41NDg2QzIxLjI1MDcgMjguMTg5NyAyMS41NDE0IDI3Ljg5ODQgMjEuOTAwMSAyNy44OTgySDI4LjQxNzdMMjguNDMyNCAyNy44NzM4QzI4Ljk1MiAyNi45NzM4IDMwLjI1MTYgMjYuOTczOCAzMC43NzEyIDI3Ljg3MzhDMzEuMjkwMyAyOC43NzM3IDMwLjY0MDMgMjkuODk4MiAyOS42MDEzIDI5Ljg5ODJDMjkuMTE5MiAyOS44OTgxIDI4LjY3MzUgMjkuNjQwOSAyOC40MzI0IDI5LjIyMzRMMjguNDE3NyAyOS4xOThIMjIuNDk4OEwyMi40OTE5IDI5LjI0QzIxLjk1OTcgMzIuMzg5NyAxOC4yMTc0IDMzLjc4MjIgMTUuNzU1NiAzMS43NDY4QzE0LjA0ODEgMzAuMzM0NyAxMy43NTA4IDI3LjgyOTYgMTUuMDgwOCAyNi4wNTc0Wk0yMC42NjA5IDE2Ljk5QzIyLjU1NTQgMTQuNDE3OSAyNi41MjQxIDE0Ljg2MTIgMjcuODA0NCAxNy43ODc4QzI3Ljg5NzMgMTguMDAwOCAyNy44NjkxIDE4LjI0NzQgMjcuNzMxMiAxOC40MzQzQzI3LjQzNCAxOC44MzY0IDI2LjgxMzQgMTguNzY2NCAyNi42MTMgMTguMzA4M1YxOC4zMDc0QzI1Ljk0NTQgMTYuNzc5MyAyNC4xMTM0IDE2LjE0ODUgMjIuNjQ2MiAxNi45NDEyQzIwLjcxNjIgMTcuOTg0IDIwLjYzODYgMjAuNzI1NiAyMi41MDY2IDIxLjg3NTdDMjIuODEyIDIyLjA2MzkgMjIuOTA3MyAyMi40NjM3IDIyLjcxOTUgMjIuNzY5M0wxOS41ODk2IDI3Ljg1NjJMMTkuNTc1IDI3Ljg4MDZMMTkuNTg4NiAyNy45MDZDMjAuMDc1IDI4LjgwNTUgMTkuNDIzNiAyOS44OTgzIDE4LjQwMTEgMjkuODk4MkMxNy45MTk2IDI5Ljg5NzggMTcuNDc0MyAyOS42NDEyIDE3LjIzMzIgMjkuMjI0NEMxNi43MTI5IDI4LjMyNDggMTcuMzYxOSAyNy4xOTg4IDE4LjQwMTEgMjcuMTk4SDE4LjQ2NzVMMTguNDgyMiAyNy4xNzQ2TDIxLjI3NjEgMjIuNjM1NUwyMS4yOTg2IDIyLjU5OTRMMjEuMjY2NCAyMi41NzFDMTkuNjQ2MSAyMS4xNTAxIDE5LjM4MjkgMTguNzI1MiAyMC42NjA5IDE2Ljk5WiIgZmlsbD0id2hpdGUiIHN0cm9rZT0iIzIxQTBCRCIgc3Ryb2tlLXdpZHRoPSIwLjEiLz4KPC9zdmc+Cg==",
    is_valid: true,
    label: "Webhook",
    environment: "onprem",
    description: "Custom HTTP input trigger",
    long_description: "Execute a workflow with an unauthicated POST request",
    id: "",
  },
  {
    name: "Schedule",
    type: "TRIGGER",
    parameters : [
      {
          "name": "cron",
          "example": "",
          "value": "*/25 * * * *"
      },
      {
          "name": "execution_argument",
          "example": "",
          "value": "",
      },
    ],
    status: "uninitialized",
    trigger_type: "SCHEDULE",
    errors: null,
    large_image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4IiByeD0iOCIgZmlsbD0iIzIxQTBCRCIvPgo8Y2lyY2xlIGN4PSIyNCIgY3k9IjI0IiByPSI4Ljc1IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuNSIvPgo8cGF0aCBkPSJNMjguNSAyNEgyNC4yNUMyNC4xMTE5IDI0IDI0IDIzLjg4ODEgMjQgMjMuNzVWMjAuNSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K",
    label: "Schedule",
    is_valid: true,
    environment: "onprem",
    description: "Schedule time trigger",
    long_description: "Create a schedule based on cron",
    id: "",
  },
  {
    name: "Pipelines",
    type: "TRIGGER",
    status: "uninitialized",
    description: "Run a pipeline trigger",
    trigger_type: "PIPELINE",
    errors: null,
    is_valid: true,
    label: "Pipeline",
    environment: "onprem",
    large_image: "/images/workflows/tenzir2.png",
    long_description: "Controls a pipeline to run things",
    id: "",
  },
  {
    name: "Shuffle Workflow",
    type: "TRIGGER",
    status: "uninitialized",
    trigger_type: "SUBFLOW",
    errors: null,
    large_image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4IiByeD0iOCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzMxN181OTcpIi8+CjxwYXRoIGQ9Ik0xNS45OTk4IDE2LjAwMTVMMTUuOTk5OCAyNS43NDA2TDE5LjIwMDEgMjUuNzQwNkwxOS4yMDAxIDE5LjI0ODVMMzEuOTk5OCAxOS4yNDg1TDMxLjk5OTggMTYuMDAxNUwxNS45OTk4IDE2LjAwMTVaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjguNzk5NCAyMi4yNjE3TDI4Ljc5OTQgMjguNzUzOEwxNS45OTk4IDI4Ljc1MzhMMTUuOTk5OCAzMi4wMDA4TDMxLjk5OTggMzIuMDAwOEwzMS45OTk4IDIyLjI2MTdMMjguNzk5NCAyMi4yNjE3WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTI1LjczOSAyMi4yNjE3TDIyLjI2MDcgMjIuMjYxN0wyMi4yNjA3IDI1Ljc0TDI1LjczOSAyNS43NEwyNS43MzkgMjIuMjYxN1oiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMzE3XzU5NyIgeDE9Ii03LjcwNzc2ZS0wOSIgeTE9IjI3LjA3NjkiIHgyPSI0OC4wMDE0IiB5Mj0iMjYuOTM3NyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRkY4NDQ0Ii8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0YyNjQzQiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo=",
    is_valid: true,
    label: "Subflow",
    environment: "onprem",
    description: "Run a Subflow trigger",
    long_description: "Execute another workflow from this workflow",
    id: "",
  },
  {
    name: "User Input",
    type: "TRIGGER",
    parameters: [
      {
          "name": "alertinfo",
          "example": "",
          "value": "## Stop or continue?\n\nDetails: $exec",
      },
      {
          "name": "options",
          "example": "",
          "value": "boolean",
      },
      {
          "name": "type",
          "example": "",
          "value": "subflow",
      },
      {
          "name": "email",
          "example": "",
          "value": "test@test.com",
      },
      {
          "name": "sms",
          "example": "",
          "value": "0000000",
      },
      {
          "name": "subflow",
          "example": "",
          "value": "",
      }
    ],
    status: "running",
    large_image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4IiByeD0iOCIgZmlsbD0iIzg2MzZCNCIvPgo8cGF0aCBkPSJNMjEuMjgyMiAxNi43VjE0LjdNMTYuNzgyMiAyMS4ySDE0Ljc4MjJNMTcuOTc5MSAyNC41MDMyTDE2LjU2NDkgMjUuOTE3NE0yNS45OTk3IDE2LjQ4MjRMMjQuNTg1NCAxNy44OTY2TTE2LjU2NDggMTYuNDgyNUwxNy45NzkgMTcuODk2N00yNS4yODk4IDMyLjM1MDRMMjYuNzc3NyAyOC40NTc4QzI2Ljk3OTYgMjcuOTI5NSAyNy40NDggMjcuNTQ5IDI4LjAwNjUgMjcuNDU5N0wzMS42MjY0IDI2Ljg4MDVDMzMuMTkwOSAyNi42MzAyIDMzLjQ1ODUgMjQuNDkxNiAzMi4wMDQgMjMuODYzNEwyMi4zNDUyIDE5LjY5MjdDMjEuMTc1MSAxOS4xODc0IDE5LjkxNzkgMjAuMjAxMyAyMC4xNjQgMjEuNDUxOUwyMi4yNTczIDMyLjA5MDhDMjIuNTY0MyAzMy42NTEgMjQuNzIyMSAzMy44MzU4IDI1LjI4OTggMzIuMzUwNFoiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS4yNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+Cjwvc3ZnPgo=",
    description: "Wait for user input trigger",
    trigger_type: "USERINPUT",
    is_valid: true,
    errors: null,
    label: "User input",
    environment: "cloud",
    long_description: "Take user input to continue execution",
    id: "",
  }
];
// Adds specific text to items

// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react
function useWindowSize() {
  const [size, setSize] = useState([0, 0]);
  useLayoutEffect(() => {
    function updateSize() {
      setSize([window.innerWidth, window.innerHeight]);
    }

    window.addEventListener("resize", updateSize);
    updateSize();
    return () => window.removeEventListener("resize", updateSize);
  }, []);
  return size;
}

export function sortByKey(array, key) {
  if (array === undefined) {
    return [];
  }

  if (key.startsWith("-") && key.length > 2) {
    key = key.slice(1, key.length);
    return array
      .sort(function (a, b) {
        var x = a[key];
        var y = b[key];
        return x < y ? -1 : x > y ? 1 : 0;
      })
      .reverse();
  }

  if (array === undefined || array === null) {
    return [];
  }

  return array.sort(function (a, b) {
    var x = a[key];
    var y = b[key];
    return x < y ? -1 : x > y ? 1 : 0;
  });
}

// look for keys and set values with shuffle dotnotation 
// used primarily for AI autocompletions
export function SetJsonDotnotation(jsonInput, inputKey) {

  if (jsonInput === undefined || jsonInput === null) {
    return jsonInput;
  }

  // Check for array
  if (Array.isArray(jsonInput)) {
    for (var i = 0; i < jsonInput.length; i++) {
      jsonInput[i] = SetJsonDotnotation(jsonInput[i], inputKey + ".#");
    }

    return jsonInput;
    // Check for dict
  } else if (typeof jsonInput === "object") {
    // Loop keys and values

    for (var key in jsonInput) {
      if (!jsonInput.hasOwnProperty(key)) {
        continue
      }

      const value = jsonInput[key];
      // Check if array
      if (Array.isArray(value)) {
        for (var i = 0; i < value.length; i++) {
          jsonInput[key][i] = SetJsonDotnotation(jsonInput[key][i], inputKey + "." + key + ".#");
        }
      } else if (typeof value === "object") {
        jsonInput[key] = SetJsonDotnotation(jsonInput[key], inputKey + "." + key);
      } else {
        jsonInput[key] = inputKey + "." + key
      }
    }
  } else {
    //jsonInput = inputKey

    console.log("SetJsonDotnotation: jsonInput is not an object or array, but key ", jsonInput, typeof jsonInput);
  }

  return jsonInput;
}


//export const green = "#86c142";
export const green = "#2BC07E"
export const yellow = "#FFC633";
export const red = "#F53434";
export const grey = "#b0b0b0";

export function removeParam(key, sourceURL) {
  if (sourceURL === undefined) {
    return;
  }

  var rtn = sourceURL.split("?")[0],
    param,
    params_arr = [],
    queryString = sourceURL.indexOf("?") !== -1 ? sourceURL.split("?")[1] : "";

  if (queryString !== "") {
    params_arr = queryString.split("&");
    for (let i = params_arr.length - 1; i >= 0; i -= 1) {
      param = params_arr[i].split("=")[0];
      if (param === key) {
        params_arr.splice(i, 1);
      }
    }
    rtn = rtn + "?" + params_arr.join("&");
  }

  if (rtn === "?") {
    return "";
  }

  return rtn;
}

const ACTION_STATES_STORAGE_KEY = "shuffle_action_tab_states";

export function getActionState(actionId, workflowId = null) {
  const defaultState = {
    tab: "setup",
    showOptionalParams: false,
    userSelectedTab: false,
  };

  try {
    const stored = localStorage.getItem(ACTION_STATES_STORAGE_KEY);
    if (!stored) return defaultState;

    const allStates = JSON.parse(stored);
    
    // Handle both old flat structure and new nested structure for backward compatibility
    let actionState = {};
    if (workflowId && allStates[workflowId] && allStates[workflowId][actionId]) {
      // New nested structure: workflowId -> actionId -> state
      actionState = allStates[workflowId][actionId];
    }

    // Validate tab value
    const validTabs = ["setup", "configuration"];
    const tab = validTabs.includes(actionState.tab) ? actionState.tab : defaultState.tab;

    return {
      tab,
      showOptionalParams: actionState.showOptionalParams === true,
      userSelectedTab: actionState.userSelectedTab === true,
    };
  } catch (e) {
    return defaultState;
  }
}

export function setActionState(actionId, updates, workflowId = null) {
  if (!actionId || !updates) return;

  try {
    const stored = localStorage.getItem(ACTION_STATES_STORAGE_KEY);
    const allStates = stored ? JSON.parse(stored) : {};

    if (workflowId) {
      // New nested structure: workflowId -> actionId -> state
      if (!allStates[workflowId]) {
        allStates[workflowId] = {};
      }
      if (!allStates[workflowId][actionId]) {
        allStates[workflowId][actionId] = {};
      }
      Object.assign(allStates[workflowId][actionId], updates);
    }

    localStorage.setItem(ACTION_STATES_STORAGE_KEY, JSON.stringify(allStates));
  } catch (e) {
    console.error("Failed to save action state:", e);
  }
}

const splitter = "|~|";
const svgSize = 24;
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);

//const referenceUrl = "https://shuffler.io/functions/webhooks/"
//const referenceUrl = window.location.origin+"/api/v1/hooks/"

const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const AngularWorkflow = (defaultprops) => {
  const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops;
  const {themeMode, supportEmail, brandColor} = useContext(Context)
  const theme = getTheme(themeMode, brandColor)
  const referenceUrl = globalUrl + "/api/v1/hooks/";
  //const alert = useAlert()
  let navigate = useNavigate();
  const params = useParams();
  var props = JSON.parse(JSON.stringify(defaultprops))
  props.match = {}
  props.match.params = params

  const { setLeftSideBarOpenByClick, leftSideBarOpenByClick, windowWidth } = useContext(Context)
  const [workflowAsCode, setWorkflowAsCode] = useState(false);

  var to_be_copied = "";
  const [firstrequest, setFirstrequest] = React.useState(true);
  const [apps, setApps] = React.useState([]);

  const cystyle = useMemo(() => defaultCytoscapeStyle(theme, apps), [themeMode, apps]);
  const [cy, setCy] = React.useState();

  const useStyles = makeStyles({
    notchedOutline: {
      borderColor: "#FF8544 !important",
    },
    root: {
      "& .MuiAutocomplete-listbox": {
        border: "2px solid #FF8544",
        color: theme.palette.text.primary,
        fontSize: 18,
        "& li:nth-child(even)": {
          backgroundColor: "#CCC",
        },
        "& li:nth-child(odd)": {
          backgroundColor: "#FFF",
        },
      },
    },
    inputRoot: {
      color: theme.palette.text.primary,
      "&:hover .MuiOutlinedInput-notchedOutline": {
        borderColor: "#f86a3e",
      },
    },
  });

  const [toolsApp, setToolsApp] = React.useState({});
  const [currentView, setCurrentView] = React.useState(0);
  const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
  const [workflows, setWorkflows] = React.useState([]);
  const [parentWorkflows, setParentWorkflows] = React.useState([]);
  const [showEnvironment, setShowEnvironment] = React.useState(false);
  const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);

  const [workflow, setWorkflow] = React.useState({});
  const [currentWorkflow, setCurrentWorkflow] = React.useState({}); // only for suborg distribution
  const [originalWorkflow, setOriginalWorkflow] = React.useState({});
  const [originalSelectedEnvironment, setOriginalSelectedEnvironment] = React.useState({});
  const [subworkflow, setSubworkflow] = React.useState({});
  const [subworkflowStartnode, setSubworkflowStartnode] = React.useState("");
  const [leftViewOpen, setLeftViewOpen] = React.useState(isMobile ? false : true);
  const [leftBarSize, setLeftBarSize] = React.useState(isMobile ? 0 : 235)
  const [creatorProfile, setCreatorProfile] = React.useState({});
  const [usecases, setUsecases] = React.useState([]);
  const [files, setFiles] = React.useState({
    "namespaces": [
      "default",
    ]
  });

  const [appGroup, setAppGroup] = React.useState([]);
  const [triggerGroup, setTriggerGroup] = React.useState([]);
  const [executionText, setExecutionText] = React.useState("");
  const [executionRequestStarted, setExecutionRequestStarted] = React.useState(false);

  const [scrollConfig, setScrollConfig] = React.useState({
    top: 0,
    left: 0,
    selected: "",
  });

  const [history, setHistory] = React.useState([]);
  const [historyIndex, setHistoryIndex] = React.useState(history.length);
  const [variableInfo, setVariableInfo] = React.useState({})
  const [selectedVersion, setSelectedVersion] = React.useState(null)
  const [selectedTriggerValue, setSelectedTriggerValue] = React.useState("")
  const [appAuthentication, setAppAuthentication] = React.useState(undefined);
  const [variablesModalOpen, setVariablesModalOpen] = React.useState(false);
  const [aiQueryModalOpen, setAiQueryModalOpen] = React.useState(false)
  const [workflowGenerationModalOpen, setWorkflowGenerationModalOpen] = React.useState(false);
  const [workflowDescription, setWorkflowDescription] = React.useState("");
  const [executionVariablesModalOpen, setExecutionVariablesModalOpen] =
    React.useState(false);
  const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false);

  const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false);
  const [authenticationType, setAuthenticationType] = React.useState("");


  const [workflowDone, setWorkflowDone] = React.useState(false);
  const [localFirstrequest, setLocalFirstrequest] = React.useState(true);
  const [requiresAuthentication, setRequiresAuthentication] = React.useState(false);
  const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false);
  const [showSkippedActions, setShowSkippedActions] = React.useState(false);
  const [lastExecution, setLastExecution] = React.useState("");
  const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = React.useState(false);
  const [autoCompleting, setAutocompleting] = React.useState(false);

  const [authgroupModalOpen, setAuthgroupModalOpen] = React.useState(false);
  const [authGroups, setAuthGroups] = React.useState([])

  const curpath = typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname;
  const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;


  // 0 = normal, 1 = just done, 2 = normal
  const [savingState, setSavingState] = React.useState(0);

  const [selectedResult, setSelectedResult] = React.useState({});
  const [codeModalOpen, setCodeModalOpen] = React.useState(false);

  const [variableAnchorEl, setVariableAnchorEl] = React.useState(null);

  const [sourceValue, setSourceValue] = React.useState({});
  const [destinationValue, setDestinationValue] = React.useState({});
  const [conditionValue, setConditionValue] = React.useState({});
  const [dragging, setDragging] = React.useState(false);
  const [showWorkflowRevisions, setShowWorkflowRevisions] = React.useState(false);
  const [dragPosition, setDragPosition] = React.useState({
    x: 0,
    y: 0,
  });

  // Trigger stuff
  const [selectedComment, setSelectedComment] = React.useState({});
  const [selectedTrigger, setSelectedTrigger] = React.useState({});
  const [selectedTriggerIndex, setSelectedTriggerIndex] = React.useState({});
  const [selectedEdge, setSelectedEdge] = React.useState({});
  const [selectedEdgeIndex, setSelectedEdgeIndex] = React.useState({});
  const [activeDialog, setActiveDialog] = React.useState("");
  const [visited, setVisited] = React.useState([]);
  const [allRevisions, setAllRevisions] = useState([])
  const [menuPosition, setMenuPosition] = useState(null);
  const [showDropdown, setShowDropdown] = React.useState(false);
  const [triggerActionList, setTriggerActionList] = React.useState([]);

  const [filteredApps, setFilteredApps] = React.useState([]);
  const [prioritizedApps, setPrioritizedApps] = React.useState([])

  const [environments, setEnvironments] = React.useState([]);
  const [established, setEstablished] = React.useState(false);
  const [setupSent, setSetupSent] = React.useState(false);

  const [graphSetup, setGraphSetup] = React.useState(false);

  const [selectedApp, setSelectedApp] = React.useState({});
  const [selectedAction, setSelectedAction] = React.useState({});
  const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
  const [selectedMeta, setSelectedMeta] = React.useState(undefined);

  // Disabled streaming for now
  const [streamDisabled, setStreamDisabled] = React.useState(true)


  const [executionRequest, setExecutionRequest] = React.useState({});

  const [executionRunning, setExecutionRunning] = React.useState(false);
  const [executionModalOpen, setExecutionModalOpen] = React.useState(false);
  const [executionModalView, setExecutionModalView] = React.useState(0);
  const [executionData, setExecutionData] = React.useState({});
  const [appsLoaded, setAppsLoaded] = React.useState(false);
  const [showVideo, setShowVideo] = React.useState("");
  const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false);
  const [userediting, setUserediting] = React.useState(false)

  const [lastSaved, setLastSaved] = React.useState(true);
  const [selectionOpen, setSelectionOpen] = React.useState(false);

  // eslint-disable-next-line no-unused-vars
  const [_, setUpdate] = useState(""); // Used to force rendring, don't remove

  const [executionFilter, setExecutionFilter] = React.useState("ALL")
  const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
  const [executionTimeline, setExecutionTimeline] = React.useState([]);
  const [workflowExecutionCount, setWorkflowExecutionCount] = React.useState(0);
  const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0);
  const [workflowRecommendations, setWorkflowRecommendations] = React.useState(undefined);
  const [showErrors, setShowErrors] = React.useState(true);
  const [highlightedApp, setHighlightedApp] = React.useState("")
  const [listCache, setListCache] = React.useState([]);
  const [selectedOption, setSelectedOption] = React.useState("");
  const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false);
  const [rules, setRules] = React.useState([]);
  const [sigmaFilesNames, setSigmaFileNames] = React.useState("")

  const [distributedFromParent, setDistributedFromParent] = React.useState("")
  const [suborgWorkflows, setSuborgWorkflows] = React.useState([])
  const [allTriggers, setAllTriggers] = React.useState(undefined)

  const [suggestionBox, setSuggestionBox] = React.useState({
    "position": {
      "top": 500,
      "left": 500,
    },
    "open": false,
    "attachedTo": "",
  })

  // For code editor
  const [codeEditorModalOpen, setCodeEditorModalOpen] = React.useState(false);
  const [codedata, setcodedata] = React.useState("");
  const [editorData, setEditorData] = React.useState({
    "name": "",
    "value": "",
    "field_number": -1,
    "actionlist": [],
    "field_id": "",

    "example": "",
  })
  const [executionArgumentModalOpen, setExecutionArgumentModalOpen] = React.useState(false);
  const [searchModalOpen, setSearchModalOpen] = React.useState(false);
  const [buildDebugMode, setBuildDebugMode] = useState("build"); // "build" or "debug"

  useEffect(() => {
    if (!firstrequest && isLoaded && isLoggedIn && editWorkflowModalOpen === false) {
      saveWorkflow(workflow)
    }
  }, [editWorkflowModalOpen])

  useEffect(() => {
	  // Check if selectedTriggerIndex is a number >= 0
	  if (isNaN(selectedTriggerIndex) || selectedTriggerIndex < 0) {
		  return
	  }

      if (selectedTrigger?.trigger_type === "SUBFLOW" && selectedTriggerIndex !== undefined && selectedTriggerIndex !== null && workflow?.triggers[selectedTriggerIndex].parameters.length > 1) {
        setSelectedTriggerValue(workflow.triggers[selectedTriggerIndex].parameters[1].value || "")
      } else if (selectedTrigger?.trigger_type === "USERINPUT" && selectedTriggerIndex !== undefined && selectedTriggerIndex !== null && workflow?.triggers[selectedTriggerIndex].parameters.length > 0) {
        setSelectedTriggerValue(workflow.triggers[selectedTriggerIndex].parameters[0].value || "Do you want to continue the workflow? Start parameters: $exec")
      }

  }, [selectedTriggerIndex, selectedTrigger, workflow])

  useEffect(() => {
    if(selectedEdge && Object.keys(selectedEdge).length > 0){
      setConditionsModalOpen(false)
      setCodeEditorModalOpen(false)
    }
  }, [selectedEdge])

  const dragRef = React.useRef(false);

  // Add this function to handle search
  const searchWorkflow = (workflowData, term) => {
    if (!term) return [];
    
    const results = [];
    const searchTermLower = term.toLowerCase();

    // Helper function to search through nodes
    const searchNode = (node) => {
      // Search node name/label
      if (node.label?.toLowerCase()?.replaceAll("_", " ").includes(searchTermLower) || 
          node.app_name?.toLowerCase().includes(searchTermLower)) {
        results.push({
          nodeId: node.id,
          matchType: 'nodeName',
          matchedValue: node.label || node.app_name,
          path: `${node.label?.replaceAll("_", " ")}`,
          image: node.large_image
        });
      }

      // Search parameters
      if (node.parameters) {
        node.parameters.forEach(param => {
          // Search parameter names
          if (param.name?.toLowerCase().includes(searchTermLower)) {
            results.push({
              nodeId: node.id,
              matchType: 'fieldName',
              matchedValue: param.name,
              path: `${node.label?.replaceAll("_", " ")} > ${param.name}`,
              image: node.large_image
            });
          }

          // Search parameter values
          if (param.value && typeof param.value === 'string' && 
              param.value?.toLowerCase().includes(searchTermLower)) {
            results.push({
              nodeId: node.id,
              matchType: 'fieldValue',
              matchedValue: param.value,
              path: `${node.label?.replace("_", " ")} > ${param.name}`,
              image: node.large_image
            });
          }
        });
      }
    };

    // Search through all actions
    workflowData.actions?.forEach(searchNode);
    // Search through all triggers if they exist
    workflowData.triggers?.forEach(searchNode);

    return results;
  };

  const SearchModal = memo(({ open, onClose, workflow }) => {
    const [searchTerm, setSearchTerm] = useState("");
    const [searchResults, setSearchResults] = useState([]);
    const searchTimeoutRef = useRef(null);
  
    // Custom debounce implementation
    const handleSearchChange = (event) => {
      const newTerm = event.target.value;
      setSearchTerm(newTerm);
  
      // Clear any existing timeout
      if (searchTimeoutRef.current) {
        clearTimeout(searchTimeoutRef.current);
      }
  
      // Set new timeout for search
      searchTimeoutRef.current = setTimeout(() => {
        if (!newTerm.trim()) {
          setSearchResults([]);
          return;
        }
        const results = searchWorkflow(workflow, newTerm);
        setSearchResults(results);
      }, 300); // 300ms delay
    };
  
    // Cleanup timeout on unmount
    useEffect(() => {
      return () => {
        if (searchTimeoutRef.current) {
          clearTimeout(searchTimeoutRef.current);
        }
      };
    }, []);
  
    return (
      <Dialog 
        open={open} 
        onClose={onClose}
        PaperProps={{
          style: {
            color: "white",
            minWidth: 750,
            minHeight: "200px",
            maxHeight: "80vh",
            borderRadius: 16,
            border: "1px solid var(--Container-Stroke, #494949)",
            background: "var(--Container, #000000)",
            boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)",
            position: "fixed",
            top: "70px",
            left: "50%",
            transform: "translateX(-50%)",
          },
        }}
      >
        <DialogTitle sx={{ 
          display: 'flex', 
          justifyContent: 'space-between', 
          alignItems: 'center',
          pr: 2
        }}>
          <span>Search Workflow (Beta version)</span>
          <IconButton
            onClick={onClose}
            size="small"
            sx={{ 
              color: 'white',
              '&:hover': {
                backgroundColor: 'rgba(255, 255, 255, 0.1)'
              }
            }}
          >
            <CloseIcon />
          </IconButton>
        </DialogTitle>
        <DialogContent>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
            <TextField
              autoFocus
              fullWidth
              value={searchTerm}
              onChange={handleSearchChange}
              placeholder="Search for nodes, parameters, values..."
              variant="outlined"
            />
            <div style={{ 
              overflowY: 'auto',
              display: 'flex',
              flexDirection: 'column',
              gap: '8px',
            }}>
              {searchResults.map((result, index) => (
                <Paper
                  key={index}
                  elevation={1}
                  style={{
                    padding: '12px',
                    cursor: 'pointer',
                  }}
                  onClick={() => {
                    // Find the action in workflow.actions that matches the node ID
                    const action = workflow.actions.find(action => action.id === result.nodeId);
                    if (action) {
                      console.log("Selected action", action)
                      setSelectedAction(action);
                      setRightSideBarOpen(true)
                    }
                  
                    // Navigate to the node using the utility function
                    const navigationSuccess = navigateToNode(result.nodeId);
                    
                    if (!navigationSuccess) {
                      console.warn(`Failed to navigate to node: ${result.nodeId}`);
                      // Fallback: try to trigger node selection event manually
                      if (cy && result.nodeId) {
                        const cyNode = cy.getElementById(result.nodeId);
                        if (cyNode && cyNode.length > 0) {
                          cyNode.trigger('select');
                        }
                      }
                    }

                    // Scroll to specific field if it's a field search result
                    if ((result.matchType === 'fieldName' || result.matchType === 'fieldValue') && result.path) {
                      const paramName = result.path.split(' > ').pop();
                      setTimeout(() => {
                        const field = document.querySelector(`[data-parameter="${paramName}"], [name="${paramName}"], #param_${paramName.replace(/[^a-zA-Z0-9_-]/g, '_')}`);
                        if (field) {
                          field.scrollIntoView({ behavior: 'smooth', block: 'center' });
                          field.style.border = '3px solid #2BC07E';
                          setTimeout(() => field.style.border = '', 2000);
                        }
                      }, 800);
                    }
                    
                    onClose();
                  }}
                >
                  <Box
                  sx={{
                    display: 'flex',
                    alignItems: "center",
                    gap:1,
                    mb: 1
                  }}
                  >
                  <img src={result.image} alt="Node" style={{ width: '20px', height: '20px', borderRadius: "50%", border: "1px solid #2121212" }} />
                  <Typography variant="subtitle1">
                    {result.path}
                  </Typography>
                  </Box>
                  <HighlightedValueInSearch
                  value={result.matchedValue} 
                  searchTerm={searchTerm}
                  theme={theme}
                  />
                </Paper>
              ))}
              {searchTerm && searchResults.length === 0 && (
                <Typography color="textSecondary">
                  No results found
                </Typography>
              )}
            </div>
          </div>
        </DialogContent>
      </Dialog>
    );
  });
  
  // Add keyboard shortcut handler in your main component
  useEffect(() => {
    const handleKeyPress = (event) => {
      
      if (((event.metaKey || event.ctrlKey) && event.key === 'f')) {

        // Check if any modal is currently open
        const isAnyModalOpen = codeEditorModalOpen || executionModalOpen || 
            editWorkflowModalOpen || executionArgumentModalOpen || authenticationModalOpen || codeModalOpen ||
            authgroupModalOpen;
            
        if (isAnyModalOpen) {
          return;
        }
        
        event.preventDefault();
        setSearchModalOpen(true);
      }
    };
  
    // Always add the event listener, but the handler will check permissions internally
    document.addEventListener('keydown', handleKeyPress);
    
    return () => {
      document.removeEventListener('keydown', handleKeyPress);
    };
  }, [
    userdata?.support,
    codeEditorModalOpen, 
    executionModalOpen, 
    editWorkflowModalOpen, 
    executionArgumentModalOpen, 
    authenticationModalOpen, 
    codeModalOpen, 
    authgroupModalOpen
  ]);

  // New for generated stuff
  const releaseToConnectLabel = "Release to Connect"
  const integrationApps = [
	{
		"id": "shuffle_agent",
		"name": "AI Agent",
		"type": "ACTION",
		"is_valid": true,
		"app_version": "1.0.0",
		"loop_versions": ["1.0.0"],
		"authentication": {
			"type": "",
		},
		"description": "AI Agent",
		"actions": [{
			"name": "Run LLM",
			"description": "Run an LLM query against any tool you want",
			"label": "Run LLM",
			"parameters": [
				{
					"name": "app_name",
					"value": "Shuffle AI",
					"required": true,
					"description": "The name of the app to run the LLM query against",
				},
				/*
				{
					"name": "model",
					"value": "default",
					"required": true,
					"description": "The model to use for the LLM query",
				},
				*/
				{
					"name": "input",
					"value": "What are you able to do?\n\n$exec",
					"required": true,
					"multiline": true,
					"description": "The input data for the LLM query",
				},
				{
					"name": "action",
					"value": "",
					"required": true,
					"description": "The action to perform automatically after the LLM query",
					"options": [
						"Nothing",
						"Create ticket",
						"List tickets",
						"Send Email",
						"Get specific ticket",
						"Update ticket",
						"Add ticket comment",
					],
					"multiselect": true,
				},
				{
					"name": "memory",
					"value": "",
					"required": true,
					"description": "Whether to store the conversation in memory",
					"options": [
						"Nothing",
						"Shuffle Datastore",
					],
					"multiselect": false,
					"disabled": true,
				},
				{
					"name": "knowledge",
					"value": "",
					"required": true,
					"description": "The knowledge we should inject into the context window",
					"options": [
						"Nothing",
						"Shuffle Files",
					],
					"multiselect": false,
					"disabled": true,
				},

			]
		}],
		large_image: theme.palette.singulBlackWhite,
	},
	{
    "id": "integration",
    "name": "Singul",
	"is_valid": true,
	"large_image": theme.palette.singulGreen,
    "type": "ACTION",
    "app_version": "1.0.0",
    "loop_versions": ["1.0.0"],
    "authentication": {
      "type": "",
    },
    "description": "Build & integrate tools easily with standard input and standard output. Built by Shuffle. https://singul.io",
    "actions": [
 	{
      "name": "Translate standard",
      "description": "Translates your JSON data into a standard formats, then stores it in the Shuffle Datastore",
      "label": "Translate standard",
	  "example": "{\"source_data\": \"{\\\"event\\\": \\\"login\\\", \\\"user\\\": \\\"john_doe\\\", \\\"timestamp\\\": \\\"2023-10-01T12:00:00Z\\\"}\", \"standard\": \"OCSF\"}",
      "parameters": [{
        "name": "source_data",
        "value": "",
        "required": true,
		"multiline": true,
      },
      {
        "name": "standard",
        "value": "OCSF",
		"description": "The standard to use from https://github.com/Shuffle/standards/tree/main",
		"options": [
			"OCSF"
		],
        "required": true,
        "multiline": false,
      }]
    }, {
      "name": "Cases",
      "description": "Available actions for case management",
      "label": "Cases",
      "parameters": [{
        "name": "action",
        "value": "list_tickets",
        "options": [
          "create_ticket",
          "list_tickets",
          "get_ticket",
          "close_ticket",
          "add_comment",
          "update_ticket",
          "search_tickets"
        ],
        "required": true,
      },
      {
        "name": "fields",
        "value": '{\n  "ticket_id": "123456",\n  "comment": "This is a comment"\n}',
        "required": false,
        "multiline": true,
      },
      ]
    }, {
      "name": "Communication",
      "description": "Available actions for communication",
      "label": "Communication",
      "parameters": [{
        "name": "action",
        "value": "list_messages",
        "options": [
          "send_message",
          "list_messages",
          "get_message", 
          "search_messages",
          "list_attachments",
          "get_attachment",
          "create_contact",
          "get_contact"
        ],
        "required": true,
      },
      {
        "name": "fields",
        "value": "",
        "required": false,
        "multiline": true,
      }]
    },
    {
      "name": "IAM",
      "description": "Available actions for IAM",
      "label": "IAM",
      "parameters": [{
        "name": "action",
        "value": "get_asset",
      	"options": [
			"reset_password",
			"enable_user",
			"disable_user",
			"get_identity",
			"get_asset",
			"search_identity"
		  ],
          "required": true,
      },
      {
        "name": "fields",
        "value": "",
        "required": false,
        "multiline": true,
      }]
    },
    {
      "name": "Assets",
      "description": "Available actions for Assets",
      "label": "Assets",
      "parameters": [{
        "name": "action",
        "value": "list_assets",
        "options": [
          "list_assets",
          "get_asset",
          "search_assets",
          "search_users",
          "search_endpoints",
          "search_vulnerabilities"
        ],
        "required": true,
      },
      {
        "name": "fields",
        "value": "",
        "required": false,
        "multiline": true,
      }]
    },
    {
      "name": "Eradication",
      "description": "Available actions for Eradication",
      "label": "Eradication",
      "parameters": [{
        "name": "action",
        "value": "list_alerts",
        "options": [
          "list_alerts",
          "close_alert",
          "get_alert",
          "create_detection",
          "block_hash",
          "search_hosts",
          "isolate_host",
          "unisolate_host",
          "trigger_host_scan"
        ],
        "required": true,
      },
      {
        "name": "fields",
        "value": "",
        "required": false,
        "multiline": true,
      }]
    },
    {
      "name": "Intel",
      "description": "Available actions for Intel",
      "label": "Intel",
      "parameters": [{
        "name": "action",
        "value": "get_ioc",
        "options": [
          "get_ioc",
          "create_ioc",
          "search_ioc",
          "update_ioc",
          "delete_ioc"
        ],
        "required": true,
      },
      {
        "name": "fields",
        "value": "",
        "required": false,
        "multiline": true,
      }]
    },
    {
      "name": "Network",
      "description": "Available actions for Network",
      "label": "Network",
      "parameters": [{
        "name": "action",
        "value": "get_rules",
        "options": [
          "get_rules",
          "allow_ip",
          "block_ip"
        ],
        "required": true,
      },
      {
        "name": "fields",
        "value": "",
        "required": false,
        "multiline": true,
      }]
    },
    {
      "name": "SIEM",
      "description": "Available actions for SIEM",
      "label": "SIEM",
      "parameters": [{
        "name": "action",
        "value": "search",
        "options": [
          "search",
          "list_alerts",
          "close_alert",
          "get_alert",
          "create_detection",
          "add_to_lookup_list",
          "isolate_endpoint"
        ],
        "required": true,
      },
      {
        "name": "fields",
        "value": "",
        "required": false,
        "multiline": true,
      }]
    },
	/*
	// An attempt at handling APIs directly. This ~kind of works
	{
      "name": "API",
      "description": "Attempts to take your fields and run an API call with them, whatever they are",
      "label": "Custom Action",
	  "example": "{\"source_data\": \"{\\\"event\\\": \\\"login\\\", \\\"user\\\": \\\"john_doe\\\", \\\"timestamp\\\": \\\"2023-10-01T12:00:00Z\\\"}\", \"standard\": \"OCSF\"}",
      "parameters": [
		  {
			"name": "fields",
			"value": "",
			"description": "A JSON object with the fields to send to the API. Example: {\"url\": \"hello\", \"key2\": \"value2\"}",
			"required": true,
			"multiline": true,
      	  }
	  ]
    },
	*/ 
	]}]


  const [loadedApps, setLoadedApps] = React.useState([])

  const loadAppConfig = (appId, select, skipAppLoad) => {
    if (appId === undefined || appId === null || appId.length === 0) {
      console.log("No appId to load")
      return
    }

    if (appId === "integration" || appId === "shuffle_agent") {
	  if (appId === "shuffle_agent") {
		  // Get sample apps for OpenAI, Gemini, Mistral, DeepSeek...
  		  loadAppConfig("1275a420a6a8b8d782483ac0c22f492c") // Shuffle AI 
  		  loadAppConfig("5d19dd82517870c68d40cacad9b5ca91") // OpenAI
  		  //loadAppConfig("5d19dd82517870c68d40cacad9b5ca91") // Gemini
  		  //loadAppConfig("5d19dd82517870c68d40cacad9b5ca91") // DeepSeek 

  		  //loadAppConfig("5d19dd82517870c68d40cacad9b5ca91") // Mistral
	  }

      return
    }

	// Ensures reloads don't randomly happen
	const appsearchValue = document.getElementById("appsearch")
	if (appsearchValue !== undefined && appsearchValue !== null) {
	  if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) {
		  return
	  }
	}

    if (loadedApps.includes(appId)) {
      //console.log("App already loaded: ", appId)

      // 1. Find the app and check if it has actions
      // 2. If it doesn't have actions, reload once again 
      var should_reload = false
      for (var i = 0; i < apps.length; i++) {
        const curapp = apps[i]
        if (curapp.id !== appId) {
          continue
        }

        if (curapp.actions === undefined || curapp.actions === null || curapp.actions.length === 0 || curapp.actions.length === 1) {
          should_reload = true
          break
        }
      }

      if (!should_reload) {
        return
      }
    }

    if (!loadedApps.includes(appId)) {
      loadedApps.push(appId)
      setLoadedApps(loadedApps)
    }

	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
		headers["Org-Id"] = workflow.org_id
	}

    const appUrl = `${globalUrl}/api/v1/apps/${appId}/config?openapi=false`
    fetch(appUrl, {
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        return response.json()
      })
      .then((responseJson) => {

        if (responseJson.success === true && responseJson.app !== undefined && responseJson.app !== null && responseJson.app.length > 0) {

          // Base64 decode into json
          const foundapp = JSON.parse(atob(responseJson.app))
          var selectedAppActions = selectedApp.actions === undefined || selectedApp.actions === null ? [] : selectedApp.actions
		  if (apps !== undefined && apps !== null && apps.length > 0 && (selectedAppActions.length === 0 || selectedAppActions.length === 1)) {
			  for (var appkey in apps) {
				  const loopedApp = apps[appkey]
				  if (loopedApp.id !== appId) {
					  continue
				  }

				  if (loopedApp.actions === undefined || loopedApp.actions === null || loopedApp.actions.length === 0) {
					  break
				  }

				  if (loopedApp.actions.length > selectedAppActions.length) {
					  selectedAppActions = loopedApp.actions
				  }
					  
				  break
			  }
		  }

          if (foundapp?.actions !== undefined && foundapp?.actions !== null && foundapp?.actions?.length > selectedAppActions?.length) {
            if (select) {
              setSelectedApp(foundapp)
            }

            if ((apps === undefined || apps === null || apps.length === 0) && skipAppLoad !== true) {
				console.log("No apps to update :(")
  			  	getApps() 
				return 
            } 

            for (var i = 0; i < apps.length; i++) {
              if (apps[i].id !== foundapp.id) {
                continue
              }

              apps[i].actions = foundapp.actions
              setApps(apps)
              setFilteredApps(apps)

              // Update the local storage
              localStorage.setItem("apps", JSON.stringify(apps))
              break
            }
          } else {
			  //console.log("Found app, but no actions: ", foundapp)
		  }

          if (cy !== undefined && cy !== null) {

            // Check if any apps in the workflow has 
            cy.nodes().forEach((node) => {
              const data = node.data()
              if (data.app_id === foundapp.id) {

                if (data.name === "tmp" && data.parameters !== undefined && data.parameters !== null && data.parameters.length === 1 && data.parameters[0].name === "tmp" && foundapp.actions !== undefined && foundapp.actions !== null && foundapp.actions.length > 0) {
                  const startIndex = foundapp.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0)
                  const actionIndex = startIndex < 0 ? 0 : startIndex

                  node.data("name", foundapp.actions[actionIndex].name)
                  node.data("large_image", foundapp.large_image)
                  node.data("parameters", foundapp.actions[actionIndex].parameters)
                  node.data("finished", true)
                  node.data("category", foundapp.categories !== null && foundapp.categories !== undefined && foundapp.categories.length > 0 ? foundapp.categories[0] : "")

                  /*
                    name: app.actions[actionIndex].name,
                    label: actionLabel,
                    app_name: app.name,
                    app_version: app.app_version,
                    app_id: app.id,
                    sharing: app.sharing,
                    private_id: app.private_id,
                    description: description,
                    environment: parsedEnvironments,
                    errors: [],
                    finished: false,
                    id_: newNodeId,
                    _id_: newNodeId,
                    id: newNodeId,
                    is_valid: true,
                    type: actionType,
                    parameters: parameters,
                    isStartNode: false,
                    large_image: app.large_image,
                    run_magic_output: false,
                    authentication: [],
                    execution_variable: undefined,
                    example: example,
                    required_body_fields: app.actions[actionIndex].required_body_fields,
                    authentication_id: authId,
                    finished: false,
                    template: app.template === true ? true : false,
                    */

                  toast("REPLACING ACTIONS")
                }
              }
            })
            //if (action.app_id === same && app.actions.length === 1 && app.actions[0].parameters.length === 1 && app.actions[0].parameters[0].name === "tmp") {
          }


          // FIXME: Add it to the existing list AND update the selected app
        }

      })
      .catch((error) => {
        console.log(`Failed side-loading app ${appId}: ${error}`)
      })
  }

  useEffect(() => {
    if (workflow?.actions?.length == 1) {
      if (workflow?.actions[0].app_id == "3e320a20966d33c9b7e6790b2705f0bf") {
        setWorkflowAsCode(true);
      }
    }

    if (workflow?.suborg_distribution !== undefined && workflow?.suborg_distribution !== null && workflow?.suborg_distribution.length > 0) {
    	getChildWorkflows(workflow.id)
    }

  }, [workflow]);

  // Event for making sure app is correct
  useEffect(() => {
    if (selectedApp === undefined || selectedApp === null && selectedApp.app_name === undefined) {
      return
    }

    if (apps === undefined || apps === null || apps.length === 0) {
      return
    }

    // Handle the activation case, as they are NOT in the event management system yet 
    if (selectedApp.actions === undefined || selectedApp.actions === null || selectedApp.actions.length > 1) {
      return
    } else {

      if (selectedApp.id !== undefined && selectedApp.id !== null && selectedApp.id.length > 0) {
        loadAppConfig(selectedApp.id, true)
      }
    }

    for (let appkey in apps) {
      const curapp = apps[appkey]
      if (curapp.name !== selectedApp.name) {
        continue
      }

      if (curapp.actions !== undefined && curapp.actions !== null && curapp.actions.length > selectedApp.actions.length) {
        var foundActionIndex = -1
        for (let actionkey in curapp.actions) {
          const curaction = curapp.actions[actionkey]

          // First action with a label, as they are most used (typically)
          if (curaction.category_label !== undefined && curaction.category_label !== null && curaction.category_label.length > 0) {
            foundActionIndex = actionkey
            break
          }
        }

        if (foundActionIndex >= 0) {
          var newaction = curapp.actions[foundActionIndex]

          setNewSelectedAction({
            "target": {
              "value": newaction.name
            },
          })
        }

        setSelectedApp(curapp)
      }

      break
    }

  }, [selectedApp])

  // This should all be set once, not on every iteration
  // Use states and don't update lol
  const cloudSyncEnabled =
    props.userdata !== undefined &&
      props.userdata.active_org !== null &&
      props.userdata.active_org !== undefined
      ? props.userdata.active_org.cloud_sync === true
      : false;
  const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io";

  const appBarSize = isCloud ? 75 : 72;
  const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"];
  const unloadText = "Are you sure you want to leave without saving (CTRL+S)?";
  const classes = useStyles();

  var [bodyWidth, bodyHeight] = useWindowSize()
  const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25

  /*
  // Zoom testing to try autofixing for small screens
  if (document !== undefined && document !== null && !isMobile) {
    const currentZoom = document.body.style.zoom;

    if (bodyWidth < 1367 || bodyHeight < 769) {
      console.log("LOWER ZOOM")
      document.body.style.zoom = "80%"
      bodyWidth = bodyWidth*0.8
      bodyHeight = bodyHeight*0.8
    } else {
      console.log("RESET ZOOM")
      document.body.style.zoom = "100%"
    }
  }

  console.log("Width, height: ", bodyWidth, bodyHeight)
  */

  //console.log("Mobile: ", isMobile, bodyWidth, bodyHeight)

  const [elements, setElements] = useState([]);
  const [loopRunning, setLoopRunning] = useState(false)

  var loopRunning2 = loopRunning
  const stop = () => {
    setLoopRunning(false)
    loopRunning2 = false
  }

  const start = () => {
    setLoopRunning(true)
    loopRunning2 = true
  }

  useEffect(() => {
    if (workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0 && workflow.id === originalWorkflow.id && workflow.parentorg_workflow === "") {
      setOriginalWorkflow(workflow)
    }

    // Special multi-workflow edgecase handler for events
    if (distributedFromParent === "" && suborgWorkflows === []) {
    } else {
      if (cy !== undefined && cy !== null) {
        cy.removeListener("select");
        cy.removeListener("unselect");
        cy.removeListener("add");
        cy.removeListener("remove");
        cy.removeListener("mouseover");
        cy.removeListener("mouseout");
        cy.removeListener("drag");
        cy.removeListener("free");
        cy.removeListener("cxttap");

        setTimeout(() => {
          setupGraph(workflow)

          cy.on("select", "node", (e) => {
            onNodeSelect(e, appAuthentication);
          });
          cy.on("select", "edge", (e) => onEdgeSelect(e));

          cy.on("unselect", (e) => onUnselect(e));

          cy.on("add", "node", (e) => onNodeAdded(e));
          cy.on("add", "edge", (e) => onEdgeAdded(e));
          cy.on("remove", "node", (e) => onNodeRemoved(e));
          cy.on("remove", "edge", (e) => onEdgeRemoved(e));

          cy.on("mouseover", "edge", (e) => onEdgeHover(e));
          cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e));
          cy.on("mouseover", "node", (e) => onNodeHover(e));
          cy.on("mouseout", "node", (e) => onNodeHoverOut(e));

          // Handles dragging
          cy.on("drag", "node", (e) => {
            const node = e.target;
            // This is used to calculate the movement deltas for group/multi-drag.
            if (!node.data("dragStartPos")) {
              node.data("dragStartPos", { ...node.position() });
            }

            // Move all selected nodes together when Ctrl is held
            handleMultiDrag(e);

            // Continuously update orientation while dragging (including neighbors)
            updateNodeAndNeighborsOrientation(node);

            onNodeDrag(e, selectedAction);
          });

          cy.on("free", "node", (e) => {
            const node = e.target;
            const startPos = node.data("dragStartPos");

            if (startPos) {
              node.removeData("dragStartPos");
            }

            // Clear multidrag state
            if (cy) {
              cy.nodes().forEach((n) => n.removeData("multiDragStartPos"));
            }

            // Update orientation after drag ends (including neighbors)
            updateNodeAndNeighborsOrientation(node);

            onNodeDragStop(e, selectedAction);
          });

          cy.on("cxttap", "node", (e) => onCtxTap(e));

          cy.edgehandles({
            handleNodes: (el) => {
              if (el.isNode() &&
                el.data("buttonType") != "ACTIONSUGGESTION" &&
                !el.data("isButton") &&
                !el.data("isDescriptor") &&
                !el.data("isSuggestion") &&
                el.data("type") !== "COMMENT" &&
                el.data("type") !== "RESIZE-HANDLE") {
                return true
              }

              return false
            },
            handlePosition: (node) => {
              const orientation = node.data("flowOrientation");
              return orientation === "vertical" ? "middle top" : "right middle";
            },
            preview: true,
            toggleOffOnLeave: true,
            loopAllowed: function (node) {
              return false;
            },
          })
        }, 200)
      }
    }
  }, [workflow])

  useEffect(() => {
    // if (subflowActionList.length === 0) {
    const newActionList = [];


    if (workflowExecutions.length > 0) {
      for (let execution of workflowExecutions) {
        const execArg = execution.execution_argument;
        if (execArg && execArg.length > 0) {
          const valid = validateJson(execArg);
          if (valid.valid) {
            newActionList.push({
              type: "Runtime Argument",
              name: "Runtime Argument",
              value: "$exec",
              highlight: "exec",
              autocomplete: "exec",
              example: valid.result,
            })

            break
          }
        }
      }
    }

    // Add default Runtime Argument if none were added
    if (newActionList.length === 0) {
      newActionList.push({
        type: "Runtime Argument",
        name: "Runtime Argument",
        value: "$exec",
        highlight: "exec",
        autocomplete: "exec",
        example: "",
      })
    }
    // Add Shuffle DB with cache keys if available
    let cacheKey = {
      type: "Shuffle DB",
      name: "Shuffle DB", 
      value: "$shuffle_cache",
      highlight: "shuffle_cache",
      autocomplete: "shuffle_cache",
      example: "Hello",
    }

    if (listCache?.keys?.length > 0) {
      cacheKey.example = {};
      for (let item of listCache.keys) {
        if (item.key) {
          let itemValue = item.value ?? "";
          if (itemValue.length > 10000) {
            itemValue = "";
          }
          cacheKey.example[item.key.split(" ").join("_")] = { value: itemValue };
        }
      }
    }

    newActionList.push(cacheKey);
    // FIXME: Have previous execution values in here
 
    if (
      workflow.workflow_variables !== null &&
      workflow.workflow_variables !== undefined &&
      workflow.workflow_variables.length > 0
    ) {
      for (let varkey in workflow.workflow_variables) {
        const item = workflow.workflow_variables[varkey];
        newActionList.push({
          type: "workflow_variable",
          name: item.name,
          value: item.value,
          id: item.id,
          autocomplete: `${item.name.split(" ").join("_")}`,
          example: item.value,
        });
      }
    }

    // FIXME: Add values from previous executions if they exist
    if (
      workflow.execution_variables !== null &&
      workflow.execution_variables !== undefined &&
      workflow.execution_variables.length > 0
    ) {
      for (let varkey in workflow.execution_variables) {
        const item = workflow.execution_variables[varkey];
        newActionList.push({
          type: "execution_variable",
          name: item.name,
          value: item.value,
          id: item.id,
          autocomplete: `${item.name.split(" ").join("_")}`,
          example: "",
        });
      }
    }

    var parents = getParents(selectedTrigger);
    if (parents.length > 1) {
      for (let parentkey in parents) {
        const item = parents[parentkey];
        if (item.label === "Runtime Argument") {
          continue;
        }

        var exampledata = item.example === undefined ? "" : item.example;
        // Find previous execution and their variables
        if (workflowExecutions.length > 0) {
          // Look for the ID
          for (let execkey in workflowExecutions) {
            if (workflowExecutions[execkey].results === undefined || workflowExecutions[execkey].results === null) {
              continue
            }

            var foundResult = workflowExecutions[execkey].results.find(
              (result) => result.action.id === item.id
            )
            if (foundResult === undefined) {
              continue
            }

            const validated = validateJson(foundResult.result)
            if (validated.valid) {
              exampledata = validated.result
              break
            }
          }
        }

        // 1. Take
        const actionvalue = {
          type: "action",
          id: item.id,
          name: item.label,
          autocomplete: `${item?.label?.split(" ")?.join("_")}`,
          example: exampledata,
        }
        newActionList.push(actionvalue);
      }
    }

    setTriggerActionList(newActionList);
  }, [workflow.workflow_variables, workflow.execution_variables, workflow, selectedTrigger]);


  useEffect(() => {
	if (selectedTriggerIndex === undefined || selectedTriggerIndex === null || selectedTriggerIndex < 0) {
		return
	}
	
	// Check if the running state is correct or not according to allTriggers
	if (allTriggers !== undefined && allTriggers !== null) { 
		// Find the active trigger
		if (selectedTrigger !== undefined && selectedTrigger !== null && selectedTrigger?.id !== undefined && selectedTrigger?.id !== null) {

			var useTriggers = allTriggers
			if (allTriggers.pipelines === undefined || allTriggers.pipelines === null || allTriggers.pipelines.length === 0) { 
				useTriggers.pipelines = []
			}

			if (allTriggers.schedules === undefined || allTriggers.schedules === null || allTriggers.schedules.length === 0) {
				useTriggers.schedules = []
			}

			if (allTriggers.webhooks === undefined || allTriggers.webhooks === null || allTriggers.webhooks.length === 0) {
				useTriggers.webhooks = []
			}

			// Check pipelines, schedules and webhooks at once 
			const allTriggersInOne = useTriggers.pipelines.concat(useTriggers.schedules).concat(useTriggers.webhooks) 
			for (let triggerkey in allTriggersInOne) {
				const curtrigger = allTriggersInOne[triggerkey]
				if (curtrigger.id !== selectedTrigger.id) {
					continue
				}

				if (curtrigger.status === undefined || curtrigger.status === null) {
					continue
				}

				var changed = false
				if (selectedTrigger?.parameters === undefined || selectedTrigger?.parameters === null) {
					changed = true

					if (selectedTrigger?.trigger_type === "SCHEDULE" && curtrigger?.frequency !== undefined && curtrigger?.frequency !== null) {
						selectedTrigger.parameters = [{
							name: "cron",
							value: curtrigger.frequency,
						},
						{
							name: "execution_argument",
							value: curtrigger.argument,
						}]
					}
				}

				if (curtrigger.status !== selectedTrigger.status) {
					changed = true
					selectedTrigger.status = curtrigger.status
				}

				if (curtrigger.running !== selectedTrigger.running) {
					changed = true
					selectedTrigger.running = curtrigger.running
				}

				if (changed) {
					setSelectedTrigger(selectedTrigger)
				}

				break
			}
		}
	}

  }, [allTriggers, selectedTrigger, selectedTriggerIndex])

  useEffect(() => {
    // Current variable + future state controlled
    // This is so that the loop can stop itself as well 
    if (loopRunning && loopRunning2) {
      const intervalId = setInterval(() => {
        if (!loopRunning) {
          clearInterval(intervalId);
        }

        fetchUpdates()
      }, 3000)

      return () => clearInterval(intervalId);
    }
  }, [loopRunning])

  // No point going as fast, as the nodes aren't realtime anymore, but bulk updated.
  //const { start, stop } = useInterval({
  //  duration: 3000,
  //  startImmediate: false,
  //  callback: () => {
  //    fetchUpdates();
  //  },
  //});

  const getAppDocs = (appname, location, version) => {
    fetch(`${globalUrl}/api/v1/docs/${appname}?location=${location}&version=${version}`, {
      headers: {
        Accept: "application/json",
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status === 200) {
          //toast("Successfully GOT app "+appId)
        } else {
          //toast("Failed getting app");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success === true) {
          if (responseJson.meta !== undefined && responseJson.meta !== null && Object.getOwnPropertyNames(responseJson.meta).length > 0) {
            setSelectedMeta(responseJson.meta)
          }

          if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) {
            if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) {
              const imgRegex = /<img.*?src="(.*?)"/g;
              const newdata = responseJson.reason.replace(imgRegex, '![]($1)');

              selectedApp.documentation = newdata
              setSelectedApp(selectedApp)
              setUpdate(Math.random())
            }
          }
        }

      })
      .catch((error) => {
        toast(error.toString());
      });
  };

  useEffect(() => {
    if (authenticationModalOpen === true && selectedAction.app_name !== undefined) {
      //console.log(selectedAction)
      //console.log("APP: ", selectedApp)

      if (selectedAction.documentation === undefined || selectedAction.documentation === null || selectedAction.documentation.length === 0) {
        // SelectedApp.documentation = Markdown? If so, it works
        //
        const apptype = selectedApp.generated === false ? "python" : "openapi"
        getAppDocs(selectedAction.app_name, apptype, selectedAction.app_version)
      }
    }
  }, [authenticationModalOpen])

  const listOrgCache = (orgId) => {
    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }

    if (orgId !== undefined && orgId !== null && orgId.length > 0) {
      headers["Org-Id"] = orgId
    }

    fetch(`${globalUrl}/api/v1/orgs/${orgId}/list_cache`, {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for apps :O!");
          return;
        }

        return response.json();
      })
      .then((responseJson) => {
        setListCache(responseJson);
      })
      .catch((error) => {
        toast(error.toString());
      });
  };

  const getWorkflowExecutionCount = (workflowId) => {
    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }
 
    if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      headers["Org-Id"] = workflow.org_id
    }

    fetch(`${globalUrl}/api/v1/workflows/${workflowId}/executions/count`, {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for workflow execution count: O!");
          return;
        } else {
          return response.json();
        }
      })
      .then((responseJson) => {
        if (responseJson !== undefined) {
          setWorkflowExecutionCount(responseJson.count || 0);
        }
      })
      .catch((error) => {
        toast(error.toString());
      });
  };

  const getAvailableWorkflows = (trigger_index) => {
    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }
 
    if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      headers["Org-Id"] = workflow.org_id
    }
          
	setWorkflows([])

    fetch(globalUrl + "/api/v1/workflows?subflow=true", {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for workflows :O!");
          return;
        }
        return response.json();
      })
      .then((responseJson) => {
        if (responseJson !== undefined) {

          // Sets up subflow trigger with the right info
          if (isNaN(trigger_index) === false && trigger_index > -1) {

            var baseSubflow = {}
            const trigger = workflow.triggers[trigger_index];
            if (trigger.parameters.length >= 3) {
              for (let paramkey in trigger.parameters) {
                const param = trigger.parameters[paramkey];

                // User Input & Subflow nodes
                if (param.name === "workflow" || param.name === "subflow") {
                  const paramIndex = param.name === "workflow" ? 0 : 5
                  if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow?.id) {
                    if (param.value === workflow?.id) {
                      setSubworkflow(workflow);
                      baseSubflow = workflow
                    } else {
                      const sub = responseJson.find((data) => data?.id === param.value);
                      if (sub !== undefined && subworkflow?.id !== sub?.id) {
                        baseSubflow = sub
                        setSubworkflow(sub);
                      }
                    }
                  }
                }

                if (param.name === "startnode" && param.value !== undefined && param.value !== null) {

                  if (Object.getOwnPropertyNames(baseSubflow).length > 0) {
                    const foundAction = baseSubflow.actions.find(action => action?.id === param.value)
                    if (foundAction !== null && foundAction !== undefined) {
                      setSubworkflowStartnode(foundAction)
                    }
                  } else {
                    setSubworkflowStartnode(param.value)
                  }
                }
              }
            }
          }

          if (workflows.length === 0) {
            //console.log("First request. Checking for parent trigger (if this is subflow")
            var parentworkflows = []
            var parent_ids = []
            for (let workflowkey in responseJson) {
              const innerworkflow = responseJson[workflowkey]

              for (let triggerkey in innerworkflow.triggers) {
                const trigger = innerworkflow.triggers[triggerkey]
                if (trigger.trigger_type === "SUBFLOW" || trigger.trigger_type === "USERINPUT") {

                  for (let paramkey in trigger.parameters) {
                    const param = trigger.parameters[paramkey]
                    if ((param.name === "workflow" || param.name === "subflow") && param.value === props.match.params.key && !parent_ids.includes(innerworkflow?.id)) {

                      parent_ids.push(innerworkflow?.id)
                      parentworkflows.push({
                        id: innerworkflow?.id,
                        name: innerworkflow.name,
                        image: innerworkflow.image,
                      })
                    }
                  }
                }
              }
            }

            if (parentworkflows.length > 0) {
              setParentWorkflows(parentworkflows.filter(wf => wf?.id !== props.match.params.key))
            }
          }

          setWorkflows(responseJson);
        }
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Workflow error: ", error.toString())
      });
  };

  function Heading(props) {
    const element = React.createElement(
      `h${props.level}`,
      { style: { marginTop: 40 } },
      props.children
    );
    return (
      <Typography>
        {props.level !== 1 ? (
          <Divider
            style={{
              width: "90%",
              marginTop: 40,
              backgroundColor: theme.palette.inputColor,
            }}
          />
        ) : null}
        {element}
      </Typography>
    );
  }

  const setNewAppAuth = (appAuthData, refresh) => {
    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }

    if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      headers["Org-Id"] = workflow.org_id
    }

    fetch(globalUrl + "/api/v1/apps/authentication", {
      method: "PUT",
      headers: headers,
      body: JSON.stringify(appAuthData),
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for setting app auth :O!");

          if (response.status === 400) {
            toast.error("Failed setting new auth. Please try again", {
              "autoClose": true,
            })
          }
        }

        return response.json();
      })
      .then((responseJson) => {
        if (!responseJson.success) {
          // Remove the timeout. Has to be clicked
          toast.error("Error: " + responseJson.reason, {
            "autoClose": false,
          })

        } else {
          if (refresh === true) {
            getAppAuthentication(true, true, true)
          } else {
            getAppAuthentication(true, false)
          }

          setAuthenticationModalOpen(false)
          // Needs a refresh with the new authentication..
          //toast("Successfully saved new app auth")
          if (configureWorkflowModalOpen === true) {
            setConfigureWorkflowModalOpen(false)

            setTimeout(() => {
              setConfigureWorkflowModalOpen(true)
            }, 1000)
          }
        }
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("New auth error: ", error.toString());
      });
  };

  const getWorkflowExecution = (id, execution_id, filter, orgId) => {
	if (id === undefined) {
		console.log("No workflow ID defined for getting executions")
		return
	}

    var url = `${globalUrl}/api/v2/workflows/${id}/executions`
    var method = "GET"
    if (filter === undefined || filter === null || filter.toUpperCase() === "ALL") {

      // Check for 
      if (executionFilter !== undefined && executionFilter !== null && executionFilter.length > 0 && filter !== "ALL") {
        filter = executionFilter
      } else {
        filter = "ALL"
      }
    }

    let headers = {
      "Content-Type": "application/json",
      Accept: "application/json",
    }

    if (currentWorkflow?.id?.length > 0 && currentWorkflow?.id !== undefined && currentWorkflow?.id !== null) {
      id = currentWorkflow.id;
    }

    if (currentWorkflow?.org_id?.length > 0 && currentWorkflow?.org_id !== undefined && currentWorkflow?.org_id !== null) {
      headers["Org-Id"] = currentWorkflow.org_id;
    }

	if (orgId !== undefined && orgId !== null && orgId.length > 0) {
		headers["Org-Id"] = orgId
	}

    var formattedBody = {
      method: method,
      headers: headers,  
      credentials: "include",
    }

    if (filter !== "ALL") {
      formattedBody.method = "POST"

      formattedBody.body = JSON.stringify({
        "status": filter,
        "workflow_id": id,
      })

      url = `${globalUrl}/api/v1/workflows/search`

    }

    fetch(url, formattedBody)
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for WORKFLOW EXECUTION :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson !== undefined && responseJson !== null && responseJson.runs !== undefined && responseJson.runs !== null) {
          responseJson.executions = responseJson.runs
        }

        if (responseJson !== undefined && responseJson !== null && responseJson.timeline !== undefined && responseJson.timeline !== null && responseJson.timeline.length > 0) {
			setExecutionTimeline(responseJson.timeline)
		}

        if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) {

          // - means it's opposite
          const newkeys = sortByKey(responseJson.executions, "-started_at");
          setWorkflowExecutions(newkeys);

          var tmpView = new URLSearchParams(cursearch).get("execution_id");
          if (execution_id !== undefined && execution_id !== null && execution_id.length > 0 && (tmpView === undefined || tmpView === null || tmpView.length === 0)) {
            tmpView = execution_id;
          }

          // Compare with currently selected item
          if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
            // Don't clean up if it's already open
            if (executionModalOpen === true) {
              return
            }

            const execution = responseJson.executions.find((data) => data.execution_id === tmpView);

            setExecutionModalOpen(true)
            if (execution !== null && execution !== undefined) {
              if (execution.execution_argument.includes("too large")) {
                setExecutionData({});
                setExecutionRunning(true);
                setExecutionRequestStarted(false);
              } else {
                setExecutionData(execution);
              }

              setExecutionModalView(1);
              setExecutionRequest({
                execution_id: execution.execution_id,
                authorization: execution.authorization,
              });

              start();

              //const newitem = removeParam("execution_id", cursearch);
              //navigate(curpath + newitem)
            } else {
              console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView)

              //setExecutionRequestStarted(true);
              const cur_execution = {
                execution_id: tmpView,
                //authorization: data.authorization,
              }
              setExecutionRunning(true);
              setExecutionModalView(1);
              setExecutionRequest(cur_execution);
              start();

              //const newitem = removeParam("execution_id", cursearch);
              //navigate(curpath + newitem)
              //setTimeout(() => {
              //  stop()
              //}, 5000);
            }
          }
        } else {
          var tmpView = new URLSearchParams(cursearch).get("execution_id");
          if (tmpView === undefined || tmpView === null || tmpView.length === 0) {
            const execution_id = tmpView;
            setExecutionModalView(1);
            setExecutionRequest({
              execution_id: execution_id,
            });

            start()
          }
        }
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Get execution error: ", error.toString());
      });
  };

  const fetchUpdates = () => {
    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }
 
    if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      headers["Org-Id"] = workflow.org_id
    }

    fetch(globalUrl + "/api/v1/streams/results", {
      method: "POST",
      headers: headers,
      body: JSON.stringify(executionRequest),
      credentials: "include",
      cors: "no-cors",
    })
      .then((response) => {
        if (response.status !== 200) {
          stop();
          //setExecutionModalView(0);
          //toast("Failed loading the workflow run")
          console.log("Status not 200 for stream results :O!");

          //const newitem = removeParam("execution_id", cursearch);
          //navigate(curpath + newitem)
        }

        return response.json();
      })
      .then((responseJson) => {
        handleUpdateResults(responseJson, executionRequest);
      })
      .catch((error) => {
        console.log("Execution result Error: ", error);
        stop();
      });
  };

  const abortExecution = () => {
    setExecutionRunning(false);

	var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }
 
    if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      headers["Org-Id"] = workflow.org_id
    }

    fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key + "/executions/" + executionRequest.execution_id + "/abort",
      {
        method: "GET",
        headers: headers,
        credentials: "include",
      }
    )
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for ABORT EXECUTION :O!");
        }

        return response.json();
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Abort error: ", error.toString());
      });
  };

  const handleCommandSubmit = (trigger) => {
    if (trigger.trigger_type !== "PIPELINE") {
      toast("Unable to save the configuration");
      return;
    }

    trigger.parameters = []

    const command = document.getElementById('sigma')?.value

    if (command) {
      trigger.parameters.push({
        name: "command",
        value: command
      })
    } else {
      toast("Please enter the comamnd");
      return;
    }

    // if (autoOffsetReset) {
    //   trigger.parameters.push({
    //     name: "auto_offset_reset",
    //     value: autoOffsetReset
    //   });
    // }

    setTenzirConfigModalOpen(false);
  };

  const handleSubmit = (trigger) => {
    if (trigger.trigger_type !== "PIPELINE") {
      toast("Unable to save the configuration");
      return;
    }
    if (selectedOption === "Kafka Queue") {
      trigger.parameters = []

      const pipeline = document.getElementById('pipeline')?.value

      setTenzirConfigModalOpen(false);
    } else if (selectedOption === "Syslog listener") {
      trigger.parameters = []

      const endpoint = document.getElementById('endpoint')?.value

      if (endpoint) {
        trigger.parameters.push({
          name: "endpoint",
          value: endpoint
        })
      } else {
        toast("Please enter your endpoint");
        return;
      }
    }

  };

  // Utility function to navigate to a specific node in the workflow
  const navigateToNode = (nodeId) => {
    if (!cy || !nodeId) {
      console.warn('Cytoscape instance not available or nodeId missing');
      return false;
    }

    try {
      // Find the node in Cytoscape
      const cyNode = cy.getElementById(nodeId);
      
      if (!cyNode || cyNode.length === 0) {
        console.warn(`Node with ID ${nodeId} not found in Cytoscape`);
        return false;
      }

      // Default options
      const defaultOptions = {
        select: true,
        unselectOthers: true,
        center: true,
        zoom: Math.max(cy.zoom(), 1),
        animationDuration: 800,
        easing: 'ease-in-out'
      };

      const config = { ...defaultOptions };

      // Unselect all currently selected nodes if requested
      if (config.unselectOthers) {
        cy.$(':selected').unselect();
      }
      
      // Select the target node if requested
      if (config.select) {
        cyNode.select();
      }
      
      // Center and zoom to the node with smooth animation if requested
      if (config.center) {
        cy.stop().animate({
          center: {
            eles: cyNode
          },
          zoom: config.zoom,
        }, {
          duration: config.animationDuration,
          easing: config.easing
        });
      }

      return true;
    } catch (error) {
      console.error('Error navigating to node:', error);
      return false;
    }
  };

  const handleColoring = (actionId, status, label) => {
    if (cy === undefined) {
      return
    }

    var currentnode = cy.getElementById(actionId);
    if (currentnode.length === 0) {
      return
      //continue;
    }

    currentnode = currentnode[0];
    const outgoingEdges = currentnode.outgoers("edge");
    const incomingEdges = currentnode.incomers("edge");

    switch (status) {
      case "EXECUTING":
        currentnode.removeClass("not-executing-highlight");
        currentnode.removeClass("success-highlight");
        currentnode.removeClass("failure-highlight");
        currentnode.removeClass("shuffle-hover-highlight");
        currentnode.removeClass("awaiting-data-highlight");
        incomingEdges.addClass("success-highlight");
        currentnode.addClass("executing-highlight");
        break;
      case "SKIPPED":
        currentnode.removeClass("not-executing-highlight");
        currentnode.removeClass("success-highlight");
        currentnode.removeClass("failure-highlight");
        currentnode.removeClass("shuffle-hover-highlight");
        currentnode.removeClass("awaiting-data-highlight");
        currentnode.removeClass("executing-highlight");
        currentnode.addClass("skipped-highlight");
        break;
      case "WAITING":
        currentnode.removeClass("not-executing-highlight");
        currentnode.removeClass("success-highlight");
        currentnode.removeClass("failure-highlight");
        currentnode.removeClass("shuffle-hover-highlight");
        currentnode.removeClass("awaiting-data-highlight");
        currentnode.addClass("executing-highlight");

        if (!visited.includes(label)) {
          if (executionRunning) {
            visited.push(label);
            setVisited(visited);
          }
        }

        // FIXME - add outgoing nodes to executing
        //const outgoingNodes = outgoingEdges.find().data().target
        if (outgoingEdges.length > 0) {
          outgoingEdges.addClass("success-highlight");
        }
        break;
      case "SUCCESS":
        currentnode.removeClass("not-executing-highlight");
        currentnode.removeClass("executing-highlight");
        currentnode.removeClass("failure-highlight");
        currentnode.removeClass("shuffle-hover-highlight");
        currentnode.removeClass("awaiting-data-highlight");
        currentnode.addClass("success-highlight");

        outgoingEdges.addClass("success-highlight");

        if (visited !== undefined && visited !== null && !visited.includes(label)) {
          if (executionRunning) {
            visited.push(label);
            setVisited(visited);
          }
        }

        // FIXME - add outgoing nodes to executing
        //const outgoingNodes = outgoingEdges.find().data().target
        if (outgoingEdges.length > 0) {
          for (let i = 0; i < outgoingEdges.length; i++) {
            const edge = outgoingEdges[i];
            const targetnode = cy.getElementById(edge.data().target);
            if (
              targetnode !== undefined &&
              !targetnode.classes().includes("success-highlight") &&
              !targetnode.classes().includes("failure-highlight")
            ) {
              targetnode.removeClass("not-executing-highlight");
              targetnode.removeClass("success-highlight");
              targetnode.removeClass("shuffle-hover-highlight");
              targetnode.removeClass("failure-highlight");
              targetnode.removeClass("awaiting-data-highlight");
              targetnode.addClass("executing-highlight");
            }
          }
        }
        break;
      case "FAILURE":
        //When status comes as failure, allow user to start workflow execution
        if (executionRunning) {
          setExecutionRunning(false);
        }

        currentnode.removeClass("not-executing-highlight");
        currentnode.removeClass("executing-highlight");
        currentnode.removeClass("success-highlight");
        currentnode.removeClass("awaiting-data-highlight");
        currentnode.removeClass("shuffle-hover-highlight");
        currentnode.addClass("failure-highlight");

        if (!visited.includes(label)) {
          //if (item.action.result !== undefined && item.action.result !== null && !item.action.result.includes("failed condition")) {
          //	toast("Error for " + item.action.label + " with result " + item.result);
          //}
          visited.push(label);
          setVisited(visited);
        }
        break;
      case "AWAITING_DATA":
        currentnode.removeClass("not-executing-highlight");
        currentnode.removeClass("executing-highlight");
        currentnode.removeClass("success-highlight");
        currentnode.removeClass("failure-highlight");
        currentnode.removeClass("shuffle-hover-highlight");
        currentnode.addClass("awaiting-data-highlight");
        break;
      default:
        currentnode.removeClass("not-executing-highlight");
        currentnode.removeClass("executing-highlight");
        currentnode.removeClass("success-highlight");
        currentnode.removeClass("failure-highlight");
        currentnode.removeClass("shuffle-hover-highlight");
        currentnode.removeClass("awaiting-data-highlight");
        currentnode.addClass("not-executing-highlight");
        //console.log("DEFAULT -> Clearing!");
        break;
    }
  }

  // Controls the colors and direction of execution results.
  const handleUpdateResults = (responseJson, executionRequest) => {
    if (responseJson === undefined || responseJson === null || responseJson.success === false) {
      stop()
      return
    }
    //console.log(responseJson)
    // Loop nodes and find results
    // Update on every interval? idk

    ReactDOM.unstable_batchedUpdates(() => {
      if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) {
        // FIXME: If another is selected, don't edit..
        // Doesn't work because this is some async garbage
        if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) {
          if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) {
            //console.log("Updating data!")
            setExecutionData(responseJson)
          } else {
            if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING" || responseJson.status === "FINISHED") {
              stop()
            }

            //console.log("NOT updating executiondata state.");
            return
          }
        }
      }

      if (responseJson.execution_id !== executionRequest.execution_id) {
        cy.elements().removeClass("success-highlight failure-highlight executing-highlight");
        return;
      }

      if (responseJson.results !== null && responseJson.results.length > 0) {
        // First clear current nodes
        if (responseJson.workflow.actions !== undefined && responseJson.workflow.actions !== null) {
          // In clearing of actions
          for (let actionKey in responseJson.workflow.actions) {
            var item = responseJson.workflow.actions[actionKey];

            handleColoring(item.id, "", item.label)
          }
        }

        for (let resultKey in responseJson.results) {
          var item = responseJson.results[resultKey];

          handleColoring(item.action.id, item.status, item.action.label)
        }
      }

      if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") {
        stop();

        if (executionRunning) {
          setExecutionRunning(false);
        }

        var curelements = cy.elements();
        for (let i = 0; i < curelements.length; i++) {
          if (curelements[i].classes().includes("executing-highlight")) {
            curelements[i].removeClass("executing-highlight");
            curelements[i].addClass("failure-highlight");
          }
        }

        getWorkflowExecution(props.match.params.key, "", executionFilter);
      } else if (responseJson.status === "FINISHED") {
        setExecutionRunning(false);
        stop();
        getWorkflowExecution(props.match.params.key, "");
        setUpdate(Math.random());
      }
    })
  };

  var streamDisabled2 = false
  const sendStreamRequest = (body) => {
    //console.log("Stream not activated yet.")
    if (!isCloud) {
      return
    }

    if (streamDisabled) {
      return
    }


    // Session may be important here huh 
    body.user_id = userdata.id

    //const url = ${globalUrl}/api/v1/workflows/${props.match.params.key}/stream
    //const streamUrl = "http://localhost:5002"

    //console.log("Stream request: ", body)
    const streamUrl = "https://stream.shuffler.io"
    const url = `${streamUrl}/api/v1/workflows/${props.match.params.key}/stream`

    var parsedbody = body
    try {
      parsedbody = JSON.stringify(body)
    } catch (e) {
      console.log("Error parsing body for stream: ", e)
    }

	var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }
 
    if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      headers["Org-Id"] = workflow.org_id
    }

    fetch(url, {
      method: "POST",
      headers: headers,
      body: parsedbody,
      credentials: "include",
    })
      .then((response) => {
        setSavingState(0);
        if (response.status !== 200) {

          setStreamDisabled(true)
          streamDisabled2 = true
          //console.log("Status not 200 for stream :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        //console.log("Stream resp: ", responseJson)
      })
      .catch((error) => {
        console.log("Stream send error: ", error.toString())
        setStreamDisabled(true)
        streamDisabled2 = true
      })
  }

  const saveWorkflow = (curworkflow, executionArgument, startNode, duplicationOrg, skip_popup) => {
    var success = false;

    if (isCloud && !isLoggedIn) {
      console.log("Should redirect to register with redirect.")

      setTimeout(() => {
        // toast("You may not have access to this workflow.")
        localStorage.setItem("redirectId", props.match.params.key)
        navigate("/register?view=workflows&message=You need sign up to use workflows with Shuffle");
        // window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle`
        // window.location.href = `/workflows`
      }, 2500)

      return
    }

    if (curworkflow === undefined || curworkflow === null) {
      console.log("No workflow during save")
      return
    }

    if (curworkflow.actions === undefined || curworkflow.actions === null || curworkflow.actions.length === 0) {
	  //toast.error("The workflow is empty. Please add at least one action.")
      return
    }

    setSavingState(2);

    // This might not be the right course of action, but seems logical, as items could be running already
    // Makes it possible to update with a version in current render
    stop();
    var useworkflow = workflow;
    if (curworkflow !== undefined) {
      useworkflow = curworkflow;
    }



    var cyelements = []
    if (cy !== undefined && cy !== null) {
      cyelements = cy.elements()
    }

    var newActions = [];
    var newTriggers = [];
    var newBranches = [];
    var newVBranches = [];
    var newComments = [];
    var newResizes = [];
    for (let cyelementsKey in cyelements) {
      if (cyelements[cyelementsKey].data === undefined) {
        continue;
      }

      var type = cyelements[cyelementsKey].data()["type"]
      if (type === undefined) {
        if (cyelements[cyelementsKey].data().source === undefined || cyelements[cyelementsKey].data().target === undefined) {
          continue
        }

        // Get the parent item
        var source_attachment = ""
        const branchSource = cy.getElementById(cyelements[cyelementsKey].data().source)
        if (branchSource === undefined || branchSource === null) {
        } else {
          const branchSourceData = branchSource.data()
          if (branchSourceData !== undefined && branchSourceData !== null && branchSourceData.attachedTo !== undefined) {
            source_attachment = branchSourceData.attachedTo

            // Check if it's the 'else' or not based on uuidv5 
            const else_attachment = uuidv5(source_attachment, uuidv5.URL)
            if (else_attachment === branchSourceData.id) {
              source_attachment = source_attachment + "-else"
            }

            console.log("Source parent: ", source_attachment)
          }
        }

        var parsedElement = {
          id: cyelements[cyelementsKey].data().id,
          source_id: cyelements[cyelementsKey].data().source,
          destination_id: cyelements[cyelementsKey].data().target,
          conditions: cyelements[cyelementsKey].data().conditions,
          decorator: cyelements[cyelementsKey].data().decorator,

          source_parent: source_attachment,
        }

        if (parsedElement.decorator) {
          newVBranches.push(parsedElement)
        } else {
          newBranches.push(parsedElement)
        }

      } else {
        if (type === "ACTION") {
          const cyelement = cyelements[cyelementsKey].data()
          const elementid =
            cyelement.id === undefined || cyelement.id === null
              ? cyelement["_id"]
              : cyelement.id;

          var curworkflowAction = useworkflow.actions.find((a) => a !== undefined && (a["id"] === elementid || a["_id"] === elementid))

          if (curworkflowAction === undefined) {
            curworkflowAction = cyelements[cyelementsKey].data()
          }

          curworkflowAction.position = cyelements[cyelementsKey].position();

          // workaround to fix some edgecases
          if (curworkflowAction.parameters === "" || curworkflowAction.parameters === null) {
            curworkflowAction.parameters = [];
          }

          if (
            curworkflowAction.example === undefined ||
            curworkflowAction.example === "" ||
            curworkflowAction.example === null
          ) {
            if (cyelements[cyelementsKey].data().example !== undefined) {
              curworkflowAction.example = cyelements[cyelementsKey].data().example;
            }
          }

          // Override just in this place
          //console.log("WORKFLOWACTION: ", curworkflowAction, "CY: ", cyelements[cyelementsKey].data())
          const foundLabel = cyelements[cyelementsKey].data("label")
          if (foundLabel !== undefined && foundLabel !== null && foundLabel.length > 0) {
            curworkflowAction.label = foundLabel
          }

          const foundAuth = cyelements[cyelementsKey].data("authentication_id")
          if (foundAuth !== undefined && foundAuth !== null && foundAuth.length > 0) {
            curworkflowAction.authentication_id = foundAuth
          }

          const executionDelay = cyelements[cyelementsKey].data("execution_delay")
          if (executionDelay !== undefined && executionDelay !== null && executionDelay.length > 0) {
            curworkflowAction.execution_delay = executionDelay
          }

          const executionVariable = cyelements[cyelementsKey].data("execution_variable")
          if (executionVariable !== undefined && executionVariable !== null) {
            curworkflowAction.execution_variable = executionVariable
          }

          const cyParams = cyelements[cyelementsKey].data("parameters")
          if (cyParams !== undefined && cyParams !== null && cyParams.length > 0) {
            curworkflowAction.parameters = cyParams
          }

          curworkflowAction.errors = [];
          curworkflowAction.isValid = true;

          // Cleans up OpenAPI items
          var newparams = [];
          for (let parametersKey in curworkflowAction.parameters) {
            const thisitem = curworkflowAction.parameters[parametersKey];
            if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) {
              continue;
            }

            if (thisitem.value !== undefined && thisitem.value !== null && Array.isArray(thisitem.value)) {
              thisitem.value = thisitem.value.join(",")
            }

            newparams.push(thisitem);
          }

          curworkflowAction.parameters = newparams;
          newActions.push(curworkflowAction);
        } else if (type === "TRIGGER") {
          if (useworkflow.triggers === undefined || useworkflow.triggers === null) {
            useworkflow.triggers = [];
          }

          var curworkflowTrigger = useworkflow.triggers.find(
            (a) => a.id === cyelements[cyelementsKey].data()["id"]
          );
          if (curworkflowTrigger === undefined) {
            curworkflowTrigger = cyelements[cyelementsKey].data();
          }

          curworkflowTrigger.position = cyelements[cyelementsKey].position();
          if (curworkflowTrigger.canConnect === false) {
            continue
          }

          newTriggers.push(curworkflowTrigger);
        } else if (type === "COMMENT") {
          if (useworkflow.comments === undefined || useworkflow.comments === null) {
            useworkflow.comments = [];
          }

          var curworkflowComment = useworkflow.comments.find(
            (a) => a.id === cyelements[cyelementsKey].data()["id"]
          )

          if (curworkflowComment === undefined) {
            curworkflowComment = cyelements[cyelementsKey].data();
            try {
              curworkflowComment.position.x = parseInt(curworkflowComment.position.x)
            } catch (e) {
              console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x)
            }

            try {
              curworkflowComment.position.y = parseInt(curworkflowComment.position.y)
            } catch (e) {
              console.log("Failed to parse position Y of comment: ", curworkflowComment.position.y)
            }
          }

          const parsedHeight = parseInt(curworkflowComment["height"])
          if (!isNaN(parsedHeight)) {
            curworkflowComment.height = parsedHeight
          } else {
            curworkflowComment.width = 150
          }

          const parsedWidth = parseInt(curworkflowComment["width"])
          if (!isNaN(parsedWidth)) {
            curworkflowComment.width = parsedWidth
          } else {
            curworkflowComment.width = 200
          }

          curworkflowComment.position = cyelements[cyelementsKey].position();
          //console.log(curworkflowComment)

          newComments.push(curworkflowComment);
        } else if (type === "RESIZE-HANDLE") {
          if (useworkflow.resizes === undefined || useworkflow.resizes === null) {
            useworkflow.resizes = [];
          }

          var curworkflowResize = useworkflow.resizes.find(
            (a) => a.id === cyelements[cyelementsKey].data()["id"]
          );

          if (curworkflowResize === undefined) {
            curworkflowResize = cyelements[cyelementsKey].data();
          }

          // Ensure width and height are properly parsed
          const parsedHeight = parseInt(curworkflowResize["height"]);
          if (!isNaN(parsedHeight)) {
            curworkflowResize.height = parsedHeight;
          } else {
            curworkflowResize.height = 150; // Default value if parsing fails
          }

          const parsedWidth = parseInt(curworkflowResize["width"]);
          if (!isNaN(parsedWidth)) {
            curworkflowResize.width = parsedWidth;
          } else {
            curworkflowResize.width = 200; // Default value if parsing fails
          }

          // Update position from Cytoscape
          curworkflowResize.position = cyelements[cyelementsKey].position();

          newResizes.push(curworkflowResize);
        }
        else {
          toast("No handler for type: " + type);
        }
      }
    }

    if (userediting === true) {
      useworkflow.user_editing = true
    }

    useworkflow.actions = newActions;
    useworkflow.triggers = newTriggers;
    useworkflow.branches = newBranches;
    useworkflow.comments = newComments;
    useworkflow.visual_branches = newVBranches;

    // Errors are backend defined
    useworkflow.errors = [];
    useworkflow.previously_saved = true;

    // Find the startnode in actions
    /*
    var foundStartNode = useworkflow.actions.find((a) => a.is_start_node === true)
    console.log("Discovered startnode: ", foundStartNode)
    if ((foundStartNode === undefined || foundStartNode === null) && useworkflow.actions.length > 0) {
      // Set a startnode
      useworkflow.actions[0].is_start_node = true
      useworkflow.start = useworkflow.actions[0].id
    }
    */

	// FIXME: What is this?
    if (cy !== undefined && cy !== null) {
      // scale: 0.3,
      // bg: "#27292d",
	  if (cy?.png !== undefined && cy?.png !== null) {
		  try {
			  const cyImageData = cy.png({
				output: "base64uri",
				maxWidth: 480,
				maxHeight: 270,
			  })

			  if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) {
				useworkflow.image = cyImageData
			  }
		  } catch (e) {
			  console.log("Failed to get image data: ", e)
		  }
	  }
    }

    if (useworkflow.id === undefined || useworkflow.id === null || useworkflow.id.length === 0) {
      useworkflow.id = props.match.params.key
    }

    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }

    if (useworkflow.org_id !== undefined && useworkflow.org_id !== null && useworkflow.org_id.length > 0) {
      headers["Org-Id"] = useworkflow.org_id
    }

    // Realtime makes the workflow if it doesn't exist
    /*
    if (duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) {
      headers["Org-Id"] = duplicationOrg
    }
    */

    setLastSaved(true);
    fetch(`${globalUrl}/api/v1/workflows/${useworkflow.id}`, {
      method: "PUT",
      headers: headers,
      body: JSON.stringify(useworkflow),
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for setting workflows :O!");
        } else {
          if (distributedFromParent === "" && suborgWorkflows === []) {
          } else {
			// Slight delay to ensure we are not too fast compared to backend goroutines.
            getChildWorkflows(useworkflow.id)
			setTimeout(() => {
            	getChildWorkflows(useworkflow.id)
			},250)
          }
        }

        return response.json();
      })
      .then((responseJson) => {
        if (useworkflow.id === originalWorkflow.id && duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) {
          //duplicateParentWorkflow(useworkflow, duplicationOrg, true)
          duplicateParentWorkflow(useworkflow, duplicationOrg, true)
        }

        if (executionArgument !== undefined && startNode !== undefined) {
          //console.log("Running execution AFTER saving");
          setSavingState(0);
          executeWorkflow(executionArgument, startNode, true, skip_popup);
          return;
        }


        if (!responseJson.success) {

          setSavingState(0);
          console.log("Workflow failed loading: ", responseJson);
          if (responseJson.reason !== undefined && responseJson.reason !== null) {
            toast.error("Failed to save: " + responseJson.reason);
          } else {
            toast.error(`Failed to save. Please contact your ${supportEmail} or your local admin if this is unexpected.`)
          }
        } else {
          setSavingState(1);

          sendStreamRequest({
            "item": "workflow",
            "type": "save",
            "id": workflow.id,
          })

          if (
            responseJson.new_id !== undefined &&
            responseJson.new_id !== null
          ) {
            window.location.pathname = "/workflows/" + responseJson.new_id;
          }

          success = true;
          if (responseJson.errors !== undefined) {
            workflow.errors = responseJson.errors;
            if (responseJson.errors.length === 0) {
              workflow.isValid = true;
              workflow.is_valid = true;

              const cyelements = cy.elements();

              for (let i = 0; i < cyelements.length; i++) {
                //cyelements[i].removeStyle();
                cyelements[i].data().is_valid = true;
                cyelements[i].data().errors = [];
              }

              for (let actionkey in workflow.actions) {
                workflow.actions[actionkey].is_valid = true;
                workflow.actions[actionkey].errors = [];
              }
            } else {
              responseJson.errors.map((error) => {
                // Find the word itself
                const wordsplit = error.split(" ")
                if (wordsplit.length < 2) {
                  return
                }

                var word = ""
                var isaction = false

                for (var mapkey in wordsplit) {
                  const inword = wordsplit[mapkey]
                  if (isaction === true) {
                    word = inword
                    break
                  }

                  if (inword.toLowerCase() === "action") {
                    isaction = true
                  }
                }

                if (word === "") {
                  return
                }

                const foundnode = cy.nodes().find((node) => {
                  const nodelabel = node.data("label")
                  if (nodelabel === undefined || nodelabel === null) {
                    return false
                  }

                  return nodelabel.toLowerCase() === word.toLowerCase()
                })

                if (foundnode !== undefined && foundnode !== null) {
                  foundnode.data("is_valid", false)

                  // FIXME: Maybe append?
                  foundnode.data("errors", [error])

                  const parsedStyle = {
                    "border-width": "2px",
                    "border-opacity": ".9",
                    "border-color": red,
                  }

                  const animationDuration = 150
                  foundnode.stop().animate(
                    {
                      style: parsedStyle,
                    },
                    {
                      duration: animationDuration,
                    }
                  )
                }
              })
            }

            setWorkflow(workflow)
          }

          setTimeout(() => {
            setSavingState(0);
          }, 1500);
          getRevisionHistory(useworkflow.id, 50, 0, useworkflow.org_id)
        }
      })
      .catch((error) => {
        setSavingState(0);
        setExecutionRequestStarted(false)
        console.log("Save workflow error: ", error.toString());
        toast.warn("Failed to save the workflow. Is the network down?")
      });

    if (originalWorkflow.id === undefined || originalWorkflow.id === null || originalWorkflow.id.length === 0 || useworkflow.id === originalWorkflow.id && workflow.parentorg_workflow === "") {
      setOriginalWorkflow(useworkflow)
    }

    return success
  };

  const fixExample = (input, required) => {
    if (input === undefined || input === null || input.length === 0) {
      return ""
    }


    // 1. Find anything matching ${variable} 
    // 2. If it is required, replace it with REQUIRED
    // 3. If it is not required, replace it with empty

    // Check if it is a string or not
    var newExample = input
    if (typeof newExample !== "string") {
      return newExample
    }

    const found = newExample.match(/\${(.*?)}/g)
    if (found === null || found === undefined || found.length === 0) {
      return newExample
    }

	/*
    for (var i = 0; i < found.length; i++) {
      //newExample = newExample.replace(found[i], "REQUIRED")
      newExample = newExample.replace(found[i], "REPLACE_ME")
    }
	*/

    return newExample
  }

  const monitorUpdates = () => {
	if (cy === undefined || cy === null) {
		console.log("No cy found to verify startnode.")
		return true 
	}

    var firstnode = cy.getElementById(workflow.start);
    if (firstnode.length === 0) {
      var found = false;
      for (let actionkey in workflow.actions) {
        if (workflow.actions[actionkey].isStartNode) {
          console.log("Updating startnode");
          workflow.start = workflow.actions[actionkey].id;
		  firstnode = cy.getElementById(workflow.actions[actionkey].id);
          found = true;
          break;
        }
      }

      if (!found) {
        return false;
      }
    }

    cy.elements().removeClass(
      "success-highlight failure-highlight executing-highlight"
    );
    firstnode[0].addClass("executing-highlight");

    return true;
  };

  const runFromHere = (curAction) => {
	if (curAction.app_id === undefined || curAction.app_id === null || curAction.app_id.length === 0) {
		toast.error(`No app id found for action. Please contact ${supportEmail} if this persists`)
		return
	}

	if (workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0) {
		curAction.source_workflow = workflow.id
	}

	// Based on the previous execution id 
	// Look for the "execution_id" parameter
    const execFound = new URLSearchParams(cursearch).get("execution_id");
	if (execFound !== undefined && execFound !== null && execFound.length > 0) {
		toast.info("Rerunning based on previously watched execution id")
		curAction.source_execution = execFound
	} else if (workflowExecutions !== undefined && workflowExecutions !== null && workflowExecutions.length > 0) {
		curAction.source_execution = workflowExecutions[0].execution_id
	} else {
		toast.error("No previous execution found. Please run the workflow first.")
		return
	}

	for (var i = 0; i < workflowExecutions.length; i++) {
		const exec = workflowExecutions[i]
		if (exec.execution_id === curAction.source_execution) {
			if (exec?.execution_argument === undefined || exec?.execution_argument === null || exec?.execution_argument.length === 0) {
				break
			}

			setExecutionText(exec.execution_argument)
			break
		}
	}

	setExecutionRunning(true)
	setExecutionRequestStarted(true)
	var headers = {
		'Content-Type': 'application/json',
		'Accept': 'application/json',
	}

    if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      headers["Org-Id"] = workflow.org_id
    }

	// Rerun makes it return execution_id + authorization
	const appRunUrl = `${globalUrl}/api/v1/apps/${curAction.app_id}/run?rerun=true`
	fetch(appRunUrl, {
		method: 'POST',
		headers: headers, 
		body: JSON.stringify(curAction),
		credentials: "include",
	})
	.then((response) => {
		setExecutionRunning(false)
		setExecutionRequestStarted(false)

		if (response.status !== 200) {
			console.log("Status not 200 for stream results :O!")
		}

		return response.json()
	})
	.then((responseJson) => {
		setExecutionRequestStarted(false)

		if (responseJson?.success === false) {
			setExecutionRunning(false)

			if (responseJson?.reason !== undefined && responseJson?.reason !== null && responseJson?.reason.length > 0) {
				toast.error(responseJson.reason)
			} else {
				toast.error(`Failed to run the action. Please try again or contact ${supportEmail}`)
			}

			return
		} else if (responseJson?.success === true && responseJson?.execution_id !== undefined && responseJson?.execution_id !== null && responseJson?.execution_id.length > 0) {
			navigate(`?execution_id=${responseJson.execution_id}&node=${curAction.id}&rerun=true`)
			setExecutionRequest({
				execution_id: responseJson.execution_id,
				authorization: responseJson.authorization,
			})

			setExecutionData({})
			setExecutionModalOpen(true)
			setExecutionModalView(1)
			start()
		}
	})
	.catch((error) => {
		toast.error("Failed to run the action. "+error.toString())

		setExecutionRunning(false)
		setExecutionRequestStarted(false)
	})
  }

  const executeWorkflow = (executionArgument, startNode, hasSaved, skip_popup) => {

    if (hasSaved === false) {
      setExecutionRequestStarted(true)
      saveWorkflow(workflow, executionArgument, startNode, undefined, skip_popup);
      //console.log("FIXME: Might have forgotten to save before executing.");
      return;
    }

    if (workflow.public) {
      toast("Save it to get a new version");
    }

    var returncheck = monitorUpdates();
    if (!returncheck) {
      toast("No startnode set.");
      return;
    }

    ReactDOM.unstable_batchedUpdates(() => {


      setVisited([])
      setExecutionRequest({})
      stop()

      // FIXME: Check if any node contains $exec in a param
      // If they do, show a popup asking if they want to execute it without an execution argument, or to use a previous one
      if (skip_popup !== true && executionArgument === undefined || executionArgument === null || executionArgument?.length === 0)

        if (workflow?.actions !== undefined && workflow?.actions !== null && workflow?.actions?.length > 0) {
          var foundmissing = false
          for (let actionkey in workflow?.actions) {
            if (workflow.actions[actionkey].parameters === undefined || workflow.actions[actionkey].parameters === null || workflow.actions[actionkey].parameters.length === 0) {
              continue
            }

            for (let paramkey in workflow.actions[actionkey].parameters) {
              const param = workflow.actions[actionkey].parameters[paramkey]
              if (param.value === undefined || param.value === null || param.value.length === 0) {
                continue
              }

              if (param.value.indexOf("$exec") !== -1) {
                foundmissing = true
                break
              }
            }

            if (foundmissing) {
              break
            }
          }

          if (foundmissing) {
            //toast("This workflow contains a node that requires an execution argument. Please provide one.")
            if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) {
              setExecutionRequestStarted(false)
              setExecutionArgumentModalOpen(true)
              return
            }

            if (workflowExecutions.length > 0) {
              setExecutionRequestStarted(false)
              setExecutionArgumentModalOpen(true)

              return
            }
          }
        }

	  if (cy !== undefined && cy !== null) {
		  var curelements = cy.elements();
		  for (let i = 0; i < curelements.length; i++) {
			curelements[i].addClass("not-executing-highlight");
		  }
	  }

      var headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
      }

      if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
        headers["Org-Id"] = workflow.org_id
      }

	  if (workflow?.id === undefined || workflow?.id === null || workflow?.id?.length === 0) {
		  console.log("No workflow id found during execution")
		  workflow.id = props.match.params.key
	  }

      const data = { execution_argument: executionArgument, start: startNode };
      // fetch(`${globalUrl}/api/v1/workflows/${props.match.params.key}/execute`,
      fetch(`${globalUrl}/api/v1/workflows/${workflow.id}/execute`,
        {
          method: "POST",
          headers: headers,
          credentials: "include",
          body: JSON.stringify(data),
        }
      )
        .then((response) => {
          setExecutionRequestStarted(false)
          if (response.status !== 200) {
            console.log("Status not 200 for WORKFLOW EXECUTION :O!");
          }

          return response.json();
        })
        .then((responseJson) => {
          if (!responseJson.success) {
            //toast("Failed to start: " + responseJson.reason);
            toast(responseJson.reason);
            //toast.error(responseJson.reason);
            setExecutionRunning(false);
            setExecutionRequestStarted(false);
            stop();

            for (let i = 0; i < curelements.length; i++) {
              curelements[i].removeClass("not-executing-highlight");
            }
            return;
          } else {
            if (responseJson.execution_id !== undefined && responseJson.execution_id !== null && responseJson.execution_id.length > 0) {
              navigate(`?execution_id=${responseJson.execution_id}`)
            }

            setExecutionRunning(true);
            setExecutionRequestStarted(false);
          }

          if (
            responseJson.execution_id === "" ||
            responseJson.execution_id === undefined ||
            responseJson.authorization === "" ||
            responseJson.authorization === undefined
          ) {
            toast("Something went wrong during execution startup");
            console.log("BAD RESPONSE FOR EXECUTION: ", responseJson);
            setExecutionRunning(false);
            setExecutionRequestStarted(false);
            stop();

            for (let i = 0; i < curelements.length; i++) {
              curelements[i].removeClass("not-executing-highlight");
            }
            return;
          }

          setExecutionRequest({
            execution_id: responseJson.execution_id,
            authorization: responseJson.authorization,
          });
          setExecutionData({});
          setExecutionModalOpen(true);
          setExecutionModalView(1);
          start();
        })
        .catch((error) => {
          //toast(error.toString());
          setExecutionRequestStarted(false)
          console.log("Execute workflow err: ", error.toString());
          toast.warn("Failed to run the workflow. Is the network down?")
        });
    })
  };

  useEffect(() => {

    const handleDebugKeyDown = (event) => {
      if ((event.metaKey || event.ctrlKey) && event.key === '/') {
        event.preventDefault(); // Prevent default browser behavior (like opening search bar)
        if (!workflow.public && !executionRequestStarted) {
          executeWorkflow(executionText, workflow.start, lastSaved);
        }
      }
      if ((event.ctrlKey || event.metaKey) && event.key === "'") {
        // Check if Ctrl (Windows/Linux) or Command (Mac) key is pressed along with '/'
        if (!workflow.public && !executionModalOpen) {
          setExecutionModalOpen(true);
          getWorkflowExecution(props.match.params.key, "");
        } else if (!workflow.public && executionModalOpen) {
          setExecutionModalOpen(false);
        }
      }

      if ((event.ctrlKey || event.metaKey) && event.key === "]") {
        console.log("Show workflow revisions key pressed")
        if (!workflow.public) {
          setShowWorkflowRevisions(true)
          //setOriginalWorkflow(workflow)
        }
      }

      if ((event.ctrlKey || event.metaKey) && event.key === ";") {
        if (!workflow.public && executionModalOpen) {
          getWorkflowExecution(props.match.params.key, "");
        }
      }

      /*
          if (( event.ctrlKey || event.metaKey ) && event.shiftKey) {
            console.log("Shift key pressed")
            if (!workflow.public && executionModalOpen) {
              setExecutionRunning(false);
              stop()
              const newitem = removeParam("execution_id", cursearch);
              navigate(curpath + newitem)
              setExecutionModalView(0);
            }
          }
      */
    };

    document.addEventListener('keydown', handleDebugKeyDown);

    return () => {
      document.removeEventListener('keydown', handleDebugKeyDown);
    }
  }, [executeWorkflow, executionText, workflow, lastSaved, executionRequestStarted])

  // This can be used to only show prioritzed ones later
  // Right now, it can prioritize authenticated ones
  //"Testing",
  //
  //

  const getAuthGroups = (orgId) => {
    setAuthGroups([])
    var headers = {
      "content-type": "application/json",
      "accept": "application/json",
    }

    if (orgId !== undefined && orgId !== null && orgId.length > 0) {
      headers["Org-Id"] = orgId
    }

    fetch(globalUrl + "/api/v1/authentication/groups", {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for app auth :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success === true) {
          setAuthGroups(responseJson.data)
        } else {
          console.log("AppAuth group loading error: " + responseJson.reason);
        }
      })
      .catch((error) => {
        setAuthGroups([]);
        console.log("AppAuth group loading error: " + error.toString());
      })
  }

  const getAppAuthentication = (reset, updateAction, closeMenu, orgId) => {
    var headers = {
      "content-type": "application/json",
      "accept": "application/json",
    }

    if (orgId !== undefined && orgId !== null && orgId.length > 0) {
      headers["Org-Id"] = orgId
    }

    fetch(globalUrl + "/api/v1/apps/authentication", {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for app auth :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        var shouldClose = false
        if (responseJson.success) {
          getAuthGroups(orgId)

          var newauth = [];
          for (let authkey in responseJson.data) {
            if (responseJson.data[authkey].defined === false) {
              continue;
            }

            newauth.push(responseJson.data[authkey]);
          }

          setAppAuthentication(newauth);

          if (cy !== undefined && cy !== null) {
            // Remove the old listener for select, run with new one
            cy.removeListener("select");

            cy.on("select", "node", (e) => onNodeSelect(e, newauth));
            cy.on("select", "edge", (e) => onEdgeSelect(e));
          }

          if (updateAction === true) {
            if (selectedApp.authentication.required) {
              // Setup auth here :)
              var appUpdates = false;
              const authenticationOptions = [];

              var tmpAuth = JSON.parse(JSON.stringify(newauth));
              var latest = 0;
              for (let authkey in tmpAuth) {
                var item = tmpAuth[authkey];

                //console.log("Got auth: ", item);

                const newfields = {};
                for (let filterkey in item.fields) {
                  newfields[item.fields[filterkey].key] = item.fields[filterkey].value;
                }

                item.fields = newfields;

                const appname = selectedApp.name.toLowerCase().replaceAll(" ", "_", -1)
                const itemname = item.app.name.toLowerCase().replaceAll(" ", "_", -1)
                if (itemname === appname) {
                  authenticationOptions.push(item);

                  // Always becoming the last one
                  if (item.edited > latest) {
                    latest = item.edited;
                    selectedAction.selectedAuthentication = item;

                    for (let actionkey in workflow.actions) {
                      const actionAppname = workflow.actions[actionkey].app_name.toLowerCase().replaceAll(" ", "_", -1)
                      if (actionAppname === appname) {
                        workflow.actions[actionkey].selectedAuthentication = item;
                        workflow.actions[actionkey].authentication_id = item.id;
                        appUpdates = true;
                      }
                    }
                  } else {
                    //console.log("Not newer: ", item.edited, " vs ", latest)
                  }
                } else {
                  //console.log("Appname is wrong: ", appname, " vs ", itemname)
                }
              }

              console.log("auth options: ", authenticationOptions)

              selectedAction.authentication = authenticationOptions
              if (selectedAction.selectedAuthentication === null || selectedAction.selectedAuthentication === undefined || selectedAction.selectedAuthentication.length === "") {
                selectedAction.selectedAuthentication = {}
              }

              if (appUpdates === true) {
                console.log("Closing auth modal: Success")

                setAuthenticationModalOpen(false);
                setSelectedAction(selectedAction);
                setWorkflow(workflow);
                saveWorkflow(workflow);

                toast("Added and updated authentication!");
                shouldClose = true
              } else {
                console.log("Closing auth modal? FAIL")

                toast.error("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted.");
                shouldClose = false
              }
            } else {
              toast("No authentication to update");
            }
          } else {
            shouldClose = true
          }

        } else {
          setAppAuthentication([])
          shouldClose = true
        }

        // Auto-closing if changes were made 
        if (closeMenu === true && shouldClose === true) {
          setAuthenticationModalOpen(false);
        }
      })
      .catch((error) => {
        setAppAuthentication([]);
        //toast("Auth loading error: " + error.toString());
        console.log("AppAuth error: " + error.toString());
      });
  };

  const getApps = () => {
	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
		headers["Org-Id"] = workflow.org_id
	}

    fetch(globalUrl + "/api/v1/apps", {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for apps. Setting to pretend apps!");

          const pretend_apps = [{
            "name": "TBD",
            "id": "TBD",
            "app_name": "TBD",
            "app_version": "TBD",
            "description": "TBD",
            "version": "TBD",
            "large_image": "",
          }]

          setAppsLoaded(true)
          setFilteredApps(pretend_apps)
          setApps(Array.prototype.concat.apply(pretend_apps, triggers))
          setPrioritizedApps(pretend_apps)

          if (isLoggedIn) {
            toast("Something went wrong while loading apps. Please refresh the window to try again.")
          }

          return
        }

        //console.log("Apps loaded. JSON decoding next")

        return response.json()
      })
      .then((responseJson) => {
        if (responseJson === null) {
          console.log("No response")
          const pretend_apps = [{
            "name": "TBD",
            "id": "TBD",
            "app_name": "TBD",
            "app_version": "TBD",
            "description": "TBD",
            "version": "TBD",
            "large_image": "",
          }]

          setAppsLoaded(true)
          setFilteredApps(pretend_apps)
          setApps(Array.prototype.concat.apply(pretend_apps, triggers))
          setPrioritizedApps(pretend_apps)
          return
        }

        if (responseJson.success === false) {
          return
        }

		for (var key in responseJson) {
			const curapp = responseJson[key]
			if (curapp?.actions === undefined || curapp?.actions === null || curapp?.actions?.length === 0 || curapp?.actions?.length === 1) {
        		loadAppConfig(curapp?.id, false, true)
			}

			if (key > 10) {
				console.log("Breaking on 10 sideloads of total", responseJson.length)
				break

			}
		}

		// Find app with ID "794e51c3c1a8b24b89ccc573a3defc47" (gmail) to force-break it,
		// Find app with ID "3e2bdf9d5069fe3f4746c29d68785a6a" (shuffle tools) to force-break it,
		// as to ensure the autocorrect works. 
		/*
		const foundAppIndex = responseJson.findIndex((app) => app.id === "3e2bdf9d5069fe3f4746c29d68785a6a")
		if (foundAppIndex !== -1) {
			//responseJson[foundAppIndex].actions = responseJson[foundAppIndex].actions.slice(0, 1)
			//console.log("Tools app: ", responseJson[foundAppIndex])
		}
		*/

        // Used for e.g. Liquid testing
        const foundTools = responseJson.find((app) => app.name === "Shuffle Tools")
        if (foundTools !== undefined && foundTools !== null) {
          setToolsApp(foundTools)
        }

        // Set localstorage for the apps in the "apps" key
        setApps(Array.prototype.concat.apply(responseJson, triggers))
        if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) {
          try {
            localStorage.setItem("apps", JSON.stringify(responseJson))
          } catch (e) {
            console.log("Failed to set apps in localstorage: ", e)
          }
        }

        var handledPrioritizedApps = responseJson.filter((app) => internalIds.includes(app.name.toLowerCase()));
        handledPrioritizedApps = [].concat(integrationApps, handledPrioritizedApps)

        if (isCloud) {
          setFilteredApps(responseJson.filter((app) => !internalIds.includes(app.name.toLowerCase())))
          setPrioritizedApps(handledPrioritizedApps)

        } else {
          const tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name.toLowerCase()))
          setFilteredApps(tmpFiltered)
          setPrioritizedApps(handledPrioritizedApps)
        }

        setAppsLoaded(true)

        // Remove all cytoscape triggers first?
        if (cy !== undefined && cy !== null) {
          cy.removeListener("select")
        }

        // Re-adding cytoscape triggers
        if (cy !== undefined && cy !== null) {
          cy.on("select", "node", (e) => {
            onNodeSelect(e, appAuthentication)
          })
        }
      })
      .catch((error) => {
        console.log("App loading error: " + error.toString())
        setAppsLoaded(true)
        //toast("App loading error: "+error.toString())
      });
  };

  // Searhc by username, userId, workflow, appId should all work
  const getUserProfile = (username, rerun) => {
    fetch(`${globalUrl}/api/v1/users/creators/${username}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for WORKFLOW EXECUTION :O!");
        }


        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success !== false) {
          console.log("Found creator: ", responseJson)
          setCreatorProfile(responseJson)
        } else {
          console.log("Couldn't find the creator profile (rerun?): ", responseJson, rerun)
          // If the current user is any of the Shuffle Creators 
          // AND the workflow doesn't have an owner: allow editing.
          // else: Allow suggestions?
          //console.log("User: ", userdata)
          //if (rerun !== true) {
          //	getUserProfile(userdata.id, true)
          //}
        }
      })
      .catch((error) => {
        console.log("Get userprofile error: ", error);
      })
  }

  const getFiles = (orgId) => {
    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }

    if (orgId !== undefined && orgId !== null && orgId.length > 0) {
      headers["Org-Id"] = orgId
    }

    fetch(globalUrl + "/api/v1/files", {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          //console.log("Status not 200 for apps :O!");
          return;
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.files !== undefined && responseJson.files !== null) {
          setFiles(responseJson);
        } else {
          setFiles({
            "namespaces": [
              "default"
            ]
          });
        }

        if (
          responseJson.namespaces !== undefined &&
          responseJson.namespaces !== null
        ) {
          //setFileNamespaces(responseJson.namespaces);
        }
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Error loading files: ", error)
      });
  };

  const onChunkedResponseComplete = (result) => {
    // Dont return until in 5 seconds without setTimeout
  }

  const onChunkedResponseError = (err) => {
    if (streamDisabled) {
      return
    }
  }


  const uuidToHSV = (uuid) => {
    // Convert the UUID to a hexadecimal string without dashes
    const uuidHex = uuid.replace(/-/g, "");

    // Take the first 6 characters of the hexadecimal UUID as the seed
    const seed = parseInt(uuidHex.slice(0, 6), 16);

    // Normalize the seed to a value between 0 and 1
    const normalizedSeed = seed / 0xFFFFFF; // 0xFFFFFF is the maximum possible value with 6 hexadecimal characters

    // Use the normalized seed to generate HSV values
    const hue = normalizedSeed; // Hue value between 0 and 1
    const saturation = 0.8; // You can adjust the saturation value as desired (between 0 and 1)
    const value = 0.8; // You can adjust the value/brightness as desired (between 0 and 1)

    // Convert HSV to RGB
    const rgb = HSVtoRGB(hue, saturation, value);

    // Scale the RGB values to the 0-255 range
    const scaledRGB = rgb.map(val => Math.round(val * 255));

    return scaledRGB;
  }

  // HSV to RGB conversion function
  const HSVtoRGB = (h, s, v) => {
    const h_i = Math.floor(h * 6);
    const f = h * 6 - h_i;
    const p = v * (1 - s);
    const q = v * (1 - f * s);
    const t = v * (1 - (1 - f) * s);

    switch (h_i % 6) {
      case 0: return [v, t, p];
      case 1: return [q, v, p];
      case 2: return [p, v, t];
      case 3: return [p, q, v];
      case 4: return [t, p, v];
      case 5: return [v, p, q];
      default: return [0, 0, 0];
    }
  }

  const rgbToHex = (rgb) => {
    // Ensure that each component is in the valid range (0-255)
    const r = Math.max(0, Math.min(255, rgb[0]));
    const g = Math.max(0, Math.min(255, rgb[1]));
    const b = Math.max(0, Math.min(255, rgb[2]));

    // Convert the RGB values to hexadecimal and pad with zeros if needed
    const rHex = r.toString(16).padStart(2, "0");
    const gHex = g.toString(16).padStart(2, "0");
    const bHex = b.toString(16).padStart(2, "0");

    // Combine the hexadecimal values to create the final color code
    const hexColor = `#${rHex}${gHex}${bHex}`;

    return hexColor.toUpperCase(); // Optionally, make the result uppercase
  }

  const getUserColor = (user_id) => {
    //return "#ffffff"
    return rgbToHex(uuidToHSV(user_id))
  }

  const hoverNode = (chunkJson) => {
    // Find the node
    if (cy === undefined || cy === null) {
      console.log("Cy is undefined or null")
      return
    }

    var node = cy.getElementById(chunkJson.id)
    if (node === undefined || node === null) {
      console.log("Node is undefined or null")
      return
    }

    const color = getUserColor(chunkJson.user_id)
    const parsedStyle = {
      "border-width": "6px",
      "border-opacity": ".7",
      "font-size": "25px",
      "border-color": color,
      "color": theme.palette.text.primary
    }

    const animationDuration = 150
    node.stop().animate(
      {
        style: parsedStyle,
      },
      {
        duration: animationDuration,
      }
    )

    // Wait 3 seconds and remove it
    setTimeout(() => {
      const parsedStyle = {
        "border-width": "1px",
        "font-size": "18px",
        "border-color": "#81c784",
      }

      node.stop().animate(
        {
          style: parsedStyle,
        },
        {
          duration: animationDuration,
        }
      )
    }, 3000)
  }

  const moveNode = (chunkJson) => {
    // Find the node
    if (cy === undefined || cy === null) {
      console.log("Cy is undefined or null")
      return
    }

    var node = cy.getElementById(chunkJson.id)
    if (node === undefined || node === null) {
      console.log("Node is undefined or null")
      return
    }

    console.log("Moving node: ", node, chunkJson)

    // Find nodes attached to the node
    node.position({
      x: chunkJson.location.x,
      y: chunkJson.location.y
    })

    const connectedNodes = cy.filter('node[attachedTo = "' + chunkJson.id + '"]')
    if (connectedNodes === undefined || connectedNodes === null) {
      console.log("Connected nodes is undefined or null")
    } else {
      console.log("Connected nodes: ", connectedNodes)
      connectedNodes.remove()
    }

    const color = getUserColor(chunkJson.user_id)
    const parsedStyle = {
      "border-width": "11px",
      "border-opacity": ".7",
      "font-size": "25px",
      "border-color": color,
    }

    const animationDuration = 150
    node.stop().animate(
      {
        style: parsedStyle,
      },
      {
        duration: animationDuration,
      }
    )

    // Wait 3 seconds and remove it
    setTimeout(() => {
      const parsedStyle = {
        "border-width": "1px",
        "font-size": "18px",
        "border-color": "#81c784",
      }

      node.stop().animate(
        {
          style: parsedStyle,
        },
        {
          duration: animationDuration,
        }
      )
    }, 3000)
  }

  const hoverEdge = (chunkJson) => {
    // Find the node
    if (cy === undefined || cy === null) {
      console.log("Cy is undefined or null")
      return
    }

    var node = cy.getElementById(chunkJson.id)
    if (node === undefined || node === null) {
      console.log("Node is undefined or null")
      return
    }

    const color = getUserColor(chunkJson.user_id)
    const parsedStyle = {
      "line-gradient-stop-positions": ["0.0", "100"],
      "line-gradient-stop-colors": [color, color],
    }

    const animationDuration = 150
    node.stop().animate(
      {
        style: parsedStyle,
      },
      {
        duration: animationDuration,
      }
    )

    // Wait 3 seconds and remove it
    setTimeout(() => {
      const parsedStyle = {
        "line-gradient-stop-positions": ["0.0", "100"],
        "line-gradient-stop-colors": ["grey", "grey"],
      }

      node.stop().animate(
        {
          style: parsedStyle,
        },
        {
          duration: animationDuration,
        }
      )
    }, 3000)
  }

  const selectNode = (chunkJson) => {
    if (cy === undefined || cy === null) {
      console.log("Cy is undefined or null")
      return
    }

    var node = cy.getElementById(chunkJson.id)
    if (node === undefined || node === null) {
      console.log("Node is undefined or null")
      return
    }

    const color = getUserColor(chunkJson.user_id)
    const parsedStyle = {
      "border-width": "11px",
      "border-opacity": ".7",
      "font-size": "25px",
      "border-color": color,
    }

    const animationDuration = 150
    node.stop().animate(
      {
        style: parsedStyle,
      },
      {
        duration: animationDuration,
      }
    )

    setTimeout(() => {
      const parsedStyle = {
        "border-width": "11px",
        "border-opacity": ".7",
        "font-size": "25px",
        "border-color": color,
      }

      node.stop().animate(
        {
          style: parsedStyle,
        },
        {
          duration: animationDuration,
        }
      )
    }, 3000)
  }

  const unselectNode = (chunkJson) => {
    if (cy === undefined || cy === null) {
      console.log("Cy is undefined or null")
      return
    }

    var node = cy.getElementById(chunkJson.id)
    if (node === undefined || node === null) {
      console.log("Node is undefined or null")
      return
    }

    const color = getUserColor(chunkJson.user_id)
    const parsedStyle = {
      "border-width": "1px",
      "font-size": "18px",
      "border-color": "#81c784",
    }

    const animationDuration = 150
    node.stop().animate(
      {
        style: parsedStyle,
      },
      {
        duration: animationDuration,
      }
    )
  }

  const addNode = (chunkJson) => {
    if (cy === undefined || cy === null) {
      console.log("Cy is undefined or null")
      return
    }

    const node = cy.getElementById(chunkJson.id)
    if (node !== undefined && node !== null) {
      return
    }

    const color = getUserColor(chunkJson.user_id)

    // Create the node and add to cytoscape
    const data = chunkJson.data
    const nodeData = {
      group: "nodes",
      data: chunkJson.data,
      position: {
        x: data.x,
        y: data.y
      },
    }

    cy.add(nodeData)

    if (workflowAsCode) {
      setWorkflowAsCode(false)
    }

    // Wait 100ms then add a style for it
    setTimeout(() => {
      const node = cy.getElementById(chunkJson.id)
      if (node === undefined || node === null) {
        console.log("Node is undefined or null during auto add from other user")
        return
      }

      const parsedStyle = {
        "border-width": "11px",
        "border-opacity": ".7",
        "font-size": "25px",
        "border-color": color,
      }

      const animationDuration = 150
      node.stop().animate(
        {
          style: parsedStyle,
        },
        {
          duration: animationDuration,
        }
      )
    }, 100)
  }

  const removeNodeStream = (chunkJson) => {
    if (cy === undefined || cy === null) {
      console.log("Cy is undefined or null")
      return
    }

    const node = cy.getElementById(chunkJson.id)
    if (node === undefined || node === null) {
      console.log("Node is undefined or null")
      return
    }

    const color = getUserColor(chunkJson.user_id)

    // Animate node, then delete 1 sec later
    const parsedStyle = {
      "border-width": "11px",
      "border-opacity": ".7",
      "font-size": "25px",
      "border-color": color,
    }

    const animationDuration = 150
    node.stop().animate(
      {
        style: parsedStyle,
      },
      {
        duration: animationDuration,
      }
    )

    setTimeout(() => {
      node.remove()
    }, 1000)
  }

  const processChunkedResponse = async (response) => {
    console.log("In process resp!")

    var text = '';
    var reader = response.body.getReader()
    var decoder = new TextDecoder();

    const appendChunks = (result) => {
      var chunk = decoder.decode(result.value || new Uint8Array, { stream: !result.done });

      if (chunk === undefined || chunk === null) {
        console.log("Chunk is undefined or null")
      }

      // Try chunk JSON loading
      try {
        var chunkJson = JSON.parse(chunk)

        if (chunkJson.success === false) {
          console.log("Chunk failed: ", chunkJson)

          if (!streamDisabled) {
            setStreamDisabled(true)
            streamDisabled2 = true
          }
          return
        }


        if (chunkJson.item !== undefined && chunkJson.item !== null && chunkJson.item !== "") {
          if (chunkJson.item === "node") {
            if (chunkJson.type === "move") {
              moveNode(chunkJson)
            } else if (chunkJson.type === "hover") {
              hoverNode(chunkJson)
            } else if (chunkJson.type === "select") {
              selectNode(chunkJson)
            } else if (chunkJson.type === "unselect") {
              unselectNode(chunkJson)
            } else if (chunkJson.type === "add") {
              addNode(chunkJson)
            } else if (chunkJson.type === "remove") {
              removeNodeStream(chunkJson)
            }
          } else if (chunkJson.item === "edge") {
            if (chunkJson.type === "hover") {
              // Same as node function?
              //hoverEdge(chunkJson)
            }
          }
        }
      } catch (e) {
        console.log("Chunk JSON error: ", e)

        if (!streamDisabled) {
          setStreamDisabled(true)
          streamDisabled2 = true
        }

        return
      }

      //data.push(chunk)
      //setData(data)

      //setUpdate(Math.random());

      //console.log('got chunk of', chunk.length, 'bytes. Value: ', chunk)
      text += chunk;
      //console.log('text so far is', text.length, 'bytes
      if (result.done) {
        console.log('returning')
        return text;
      } else {
        return readChunk()
      }
    }

    const readChunk = () => {
      return reader.read().then(appendChunks);
    }

    return readChunk();
  }

  async function fetchWithTimeout(resource, options = {}) {
    const { timeout = 8000 } = options;

    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeout);

    const response = await fetch(resource, {
      ...options,
      signal: controller.signal
    });
    clearTimeout(id);

    return response;
  }

  const startWorkflowStream = async (workflowId) => {
    if (!isCloud) {
      console.log("Not cloud, not starting workflow stream")
      return
    }

    if (streamDisabled) {
      console.log("Stream listener disabled")
      return
    }

    const timeout = 60000
    //const url = `${globalUrl}/api/v1/workflows/${workflowId}/stream`
    //const streamUrl = "https://shuffle-streaming-backend-stbuwivzoq-ew.a.run.app"
    //
    const streamUrl = "https://stream.shuffler.io"
    const url = `${streamUrl}/api/v1/workflows/${workflowId}/stream`
    while (true) {
      if (streamDisabled === true || streamDisabled2 === true) {
        console.log("Stream disabled, breaking")
        break
      }

      // Wait 1 second before next request just in case of timeouts
      await new Promise(r => setTimeout(r, 1000));
      await fetchWithTimeout(url, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
        },
        credentials: "include",
        timeout: timeout,
      })
        .then(processChunkedResponse)
        .then(onChunkedResponseComplete)
        .catch(onChunkedResponseError)
    }
  }

  const [usedSubflowApps, setUsedSubflowApps] = React.useState([]);

  const getWorkflowApps = (workflow_id) => {
    let apps = []

    if (workflow_id === "") {
      console.log("workflow_id is empty");
      return {};
    }

    fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for workflows :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        for (let index in responseJson.actions) {
          apps.push(responseJson.actions[index]);
        }

        //console.log("Setting used subflow apps: ", apps)
        setUsedSubflowApps(apps);

        return apps
      })
      .catch((error) => {
        console.log("Get workflow apps error: ", error);
      });

    return apps
  }

  const findWorkflowDiff = (parentWorkflow, childWorkflow) => {
	  // Ensures new memory is used
	  parentWorkflow = JSON.parse(JSON.stringify(parentWorkflow))
	  childWorkflow = JSON.parse(JSON.stringify(childWorkflow))

	  var diff = {
		  "different": false,
		  "environment": false,
		  "actions": [],
		  "triggers": [],
	  }

	  if (parentWorkflow.actions === undefined || parentWorkflow.actions === null || parentWorkflow.actions.length === 0) {
		  console.log("Parent workflow actions are empty")
		  return diff
	  }

	  if (childWorkflow.actions === undefined || childWorkflow.actions === null || childWorkflow.actions.length === 0) {
		  console.log("Child workflow actions are empty")
		  return diff
	  }

	  var parentEnvironment = ""
	  var childEnvironment = ""

	  if (parentWorkflow.triggers !== undefined && parentWorkflow.triggers !== null && parentWorkflow.triggers.length > 0) {
		  parentWorkflow.actions = parentWorkflow.actions.concat(parentWorkflow.triggers)
	  }

	  for (var parentKey in parentWorkflow.actions) {
		  const parentAction = parentWorkflow.actions[parentKey]
		  if (parentAction.environment !== undefined && parentAction.environment !== null && parentAction.environment !== "") {
			  parentEnvironment = parentAction.environment
		  }

		  var actionDiff = {
			  parameters: []
		  }

		  var found = false
		  if (childWorkflow.triggers !== undefined && childWorkflow.triggers !== null && childWorkflow.triggers.length > 0) {
			  for (var triggerKey in childWorkflow.triggers) {
				  if (childWorkflow.triggers[triggerKey].replacement_for_trigger !== parentAction.id) {
					  continue
				  }

				  // Rewrapping the ID just in case
				  childWorkflow.triggers[triggerKey].id = childWorkflow.triggers[triggerKey].replacement_for_trigger
				  childWorkflow.actions.push(childWorkflow.triggers[triggerKey])
				  break
			  }
		  }

		  for (var childKey in childWorkflow.actions) {
			  const childAction = childWorkflow.actions[childKey]
			  if (childAction.environment !== undefined && childAction.environment !== null && childAction.environment !== "") {
				  childEnvironment = childAction.environment
			  }

			  if (childAction.id !== parentAction.id) {
		  		  found = true 
				  continue
			  }

			  if (childAction.label !== parentAction.label) {
				  actionDiff.label_change = true
			  }

			  /*
			  if (childAction.app_id !== parentAction.app_id) {
				  actionDiff.app_id = true
			  }
			  */

			  if (childAction.app_name !== parentAction.app_name) {
				  actionDiff.app_name = true
			  }

			  if (childAction.app_version !== parentAction.app_version) {
				  actionDiff.app_version = true
			  }

			  if (childAction.name !== parentAction.name) {
				  actionDiff.name = true
			  }

			  // Irrelevant
			  //if (childAction.environment !== parentAction.environment) {
			  //    actionDiff.environment = true
			  //}

			  if (childAction.authentication_id !== parentAction.authentication_id) {
				  actionDiff.authentication_id = true
			  }

			  if (parentAction.parameters === undefined || parentAction.parameters === null || parentAction.parameters.length === 0 || childAction.parameters === undefined || childAction.parameters === null || childAction.parameters.length === 0) {
				  continue
			  }

			  var parentworkflow = ""
			  var parentstartnode = ""
			  var childworkflow = ""
			  var childstartnode = ""
			  for (var parentParamIndex in parentAction.parameters) {
				  const parentParam = parentAction.parameters[parentParamIndex]
				  for (var childParamIndex in childAction.parameters) {
					  const childParam = childAction.parameters[childParamIndex]
					  if (childParam.name !== parentParam.name) {
						  continue
					  }

					  if (childParam.name === "workflow" || childParam.name === "subflow") {
						  parentworkflow = parentParam.value
						  childworkflow = childParam.value
						  continue
					  }

					  if (childParam.name == "startnode") {
						  parentstartnode = parentParam.value
						  childstartnode = childParam.value
						  continue
					  }

					  if (childParam.value !== parentParam.value) {
						  actionDiff.parameters.push(childParam.name) 
					  }
				  }
			  }

			  if (parentworkflow !== childworkflow && parentstartnode !== childstartnode) {
				  actionDiff.parameters.push("subflow")
			  }
		  }

		  if (actionDiff.parameters.length > 0) {
			  actionDiff.params = true
		  }

		  /*
		  if (!found) {
			  actionDiff.new = true
		  }
		  */

		  if (actionDiff !== undefined && actionDiff !== null && Object.keys(actionDiff).length > 1) {
			  actionDiff.label = parentAction.label.replaceAll("_", " ")
			  actionDiff.id = parentAction.id
			  actionDiff.large_image = parentAction.large_image
			  diff.actions.push(actionDiff)
		  }
	  }

	  if (childEnvironment !== parentEnvironment) {
		  diff.environment = true
	  }


	  // loop diff and find if ANY key is true
	  for (var key in diff) {
		  try {
			  if (diff[key] === true || diff[key].length > 0) {
				  diff.different = true
				  break
			  }
		  } catch (e) {
			  console.log("Error in diff: ", e)
		  }
	  }

	  return diff 
  }

  const getChildWorkflows = (parentWorkflowId) => {
	  var originalChildWorkflows = []
	  try {
	  	  originalChildWorkflows = JSON.parse(JSON.stringify(suborgWorkflows))
	  } catch (e) {
		  console.log("Error in parsing suborg workflows: ", e)
	  }
	  setSuborgWorkflows([])
    //toast("Loading child workflows 1 (should be 2)")

    /*
    if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0) { 
      return
    }
    */

    const orgId = originalWorkflow.org_id === undefined || originalWorkflow.org_id === null || originalWorkflow.org_id === "" ? "" : originalWorkflow.org_id

    fetch(`${globalUrl}/api/v1/workflows/${parentWorkflowId}/child_workflows`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Org-Id": orgId,
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for workflows :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success !== false) {

		  // FIXME: There is a timing problem here somewhere.
		  for (var key in responseJson) {
			  const diff = findWorkflowDiff(originalWorkflow, responseJson[key])
			  if (diff !== undefined && diff !== null) {
				  responseJson[key].diff = diff
			  }
		  }

		  setTimeout(() => {
          	setSuborgWorkflows(responseJson)
		  }, 500)
        } else  {
		  	setTimeout(() => {
				setSuborgWorkflows(originalChildWorkflows)
			}, 500)
	  	}
      })
      .catch((error) => {
		setTimeout(() => {
			setSuborgWorkflows(originalChildWorkflows)
		}, 500)
        console.log("Get child workflows error: ", error);
      })
  }

  const getWorkflow = (workflow_id, sourcenode) => {
    fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for workflows :O!");

          if (response.status >= 500) {
            toast("Something went wrong while loading the workflow. Please reload.")
          } else {

            // Check for execution_id in URL
            // don't redirect if it exists
            var execFound = new URLSearchParams(cursearch).get("execution_id");
            var sessionToken = new URLSearchParams(cursearch).get("session_token");
            if (execFound === null && sessionToken === null) {

			  if (isCloud) {
				  toast.error(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds. If you recently deleted this workflow, speak with ${supportEmail} to recover it from a revision.`, {
					  autoClose: 10000,
				  })
			  } else { 
				  toast.error(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds. Contact ${supportEmail} if this is unexpected.`, {
					  autoClose: 10000,
				  })
			  }

              setTimeout(() => {
                if(!isLoggedIn){
                localStorage.setItem("redirectId", props.match.params.key);
                }
                window.location.pathname = "/workflows";
              }, 2500);

            } else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") {
              toast(`Injecting session token and reloading workflow..`)
              setTimeout(() => {
                setCookie("session_token", sessionToken, { path: "/" });
                window.location.href = "https://shuffler.io/workflows/3abdfb21-b40f-4e50-b855-ac0d62f83cbe";
              }, 2000)
            } else if (execFound !== null && response.status >= 300) {
				toast.info("Failed to load the workflow, but you may still find a list of workflow runs if you have access.")
          		setExecutionModalOpen(true)
			}
          }
        }

        // Read text from stream
        //return response.text();
        return response.json();
      })
      .then((responseJson) => {
        // Load as JSON
        if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id.length > 0 && responseJson.id !== workflow_id) {
          toast.warning("Workflow ID mismatch. Redirected to your actual workflow. This may happen due to being in the wrong org, where we load the child workflow for you automatically.", {
			  autoClose: 10000,
		  })
          navigate(`/workflows/${responseJson.id}`)
        }

        if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow !== "") {
          setDistributedFromParent(responseJson.parentorg_workflow)
        }

        if (responseJson.childorg_workflow_ids !== undefined && responseJson.childorg_workflow_ids !== null && responseJson.childorg_workflow_ids.length > 0) {
          getChildWorkflows(responseJson.id)
        } else if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow.length > 0) {
          getChildWorkflows(responseJson.parentorg_workflow)
        }

        // Not sure why this is necessary.
        if (responseJson.isValid === undefined) {
          responseJson.isValid = true;
        }

        if (responseJson.errors === undefined) {
          responseJson.errors = [];
        }

        if (responseJson.actions === undefined || responseJson.actions === null) {
          responseJson.actions = [];
        }

        if (responseJson.triggers === undefined || responseJson.triggers === null) {
          responseJson.triggers = [];
        }

        if (responseJson.org_id !== undefined && responseJson.org_id !== null) {
          listOrgCache(responseJson.org_id)
        }

        if (responseJson.sharing !== undefined && responseJson.sharing !== null && (responseJson.sharing === "form" || responseJson.sharing === "forms")) {
          if (responseJson.actions === undefined || responseJson.actions === null || responseJson.actions.length === 0) {
            navigate("/forms/" + responseJson.id)
            toast("Redirecting to Form from Workflow")
          }
        }

        // Wait for this to finish
        fetchRecommendations(responseJson)


        if (responseJson.public) {
          setAppAuthentication([])
          setLeftBarSize(300)

          if (Object.getOwnPropertyNames(creatorProfile).length === 0) {
            //getUserProfile("frikky") 
            getUserProfile(responseJson.id, false)
          }

          //{appGroup.map((data, index) => {
          //const [appGroup, setAppGroup] = React.useState([]);
          var appsFound = []
          for (let actionkey in responseJson.actions) {
            const parsedAction = responseJson.actions[actionkey]
            if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") {
              continue
            }
            if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0) {
              appsFound.push(parsedAction)
            }
          }

          setAppGroup(appsFound)

          appsFound = []
          for (let triggerkey in responseJson.triggers) {
            const parsedAction = responseJson.triggers[triggerkey]
            if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0) {
              appsFound.push(parsedAction)
            }
          }

          setTriggerGroup(appsFound)
          setWorkflows([responseJson])
          setCurrentWorkflow(responseJson)
        } else {
          getAppAuthentication()

		  var defaultEnvironmentName = undefined
		  if (responseJson.actions !== undefined && responseJson.actions !== null && responseJson.actions.length > 0) {
			  for (var i = 0; i < responseJson.actions.length; i++) {
				  const curaction = responseJson.actions[i]
				  if (curaction.environment !== undefined && curaction.environment !== null && curaction.environment.length > 0) {
					  defaultEnvironmentName = curaction.environment
					  break
				  }
			  }
		  }

          getEnvironments(responseJson.org_id, defaultEnvironmentName)

          getFiles()
          getWorkflowExecution(responseJson.id, "")
          getAvailableWorkflows(-1)
        }


        // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows.
        if (sourcenode.id !== undefined) {

          var nodefound = false;
          var target = sourcenode.parameters.find((item) => item.name === "startnode");
          console.log("Got rightclick target: ", target)
          if (target === undefined || target === null) {
            target = {
              "name": "startnode",
              "value": responseJson.start
            }
          }

          console.log(sourcenode.parameters);
          console.log(target);
          const target_id = target === undefined ? "" : target.value;
          const actions = responseJson.actions.map((action) => {
            const node = {
              group: "nodes",
            };

            // Set it dynamically?
            node.position = {
              x: sourcenode.position.x + action.position.x,
              y: sourcenode.position.y + action.position.y,
            };

            node.data = action;

            node.data.canConnect = false
            node.data.is_valid = true
            node.data.isValid = true
            node.data._id = action["id"];
            node.data.type = "ACTION";
            node.data.source_workflow = responseJson.id;
            if (action.id === target_id) {
              nodefound = true;
            }

            if (responseJson.public) {
              node.data.is_valid = true
              node.is_valid = true
            }

            var example = "";
            if (
              action.example !== undefined &&
              action.example !== null &&
              action.example.length > 0
            ) {
              example = action.example;
            }

            node.data.example = example;
            return node;
          });

          var triggers = responseJson.triggers.map((trigger) => {
            const node = {};

            console.log("Only add workflow: ", trigger.app_name)
            if (trigger.app_name !== "Shuffle Workflow" && trigger.app_name !== "User Input") {
              return null
            }

            node.position = trigger.position;
            node.data = trigger;

            node.data.canConnect = false
            node.data.id = trigger["id"];
            node.data._id = trigger["id"];
            node.data.type = "TRIGGER";

            return node;
          });

          triggers = triggers.filter((trigger) => trigger !== null);
          const insertedNodes = [].concat(actions, triggers);
          var edges = responseJson.branches.map((branch, index) => {
            const edge = {};
            var conditions = responseJson.branches[index].conditions;
            if (conditions === undefined || conditions === null) {
              conditions = [];
            }

            var label = "";
            if (conditions.length === 1) {
              label = conditions.length + " condition";
            } else if (conditions.length > 1) {
              label = conditions.length + " conditions";
            }

            const sourceFound = insertedNodes.findIndex(
              (action) => action.data.id === branch.source_id
            );
            if (sourceFound < 0) {
              return null;
            }

            const destinationFound = insertedNodes.findIndex(
              (action) => action.data.id === branch.destination_id
            );
            if (destinationFound < 0) {
              return null;
            }


            edge.data = {
              id: branch.id,
              _id: branch.id,
              source: branch.source_id,
              target: branch.destination_id,
              label: label,
              conditions: conditions,
              hasErrors: branch.has_errors,
              decorator: false,
              source_workflow: responseJson.id,
            };

            if (responseJson.public) {
              edge.data.is_valid = true
              edge.is_valid = true
            }

            return edge;
          });

          edges = edges.filter((edge) => edge !== null);
          cy.removeListener("add");
          cy.add(insertedNodes)
          cy.add(edges);

          if (nodefound === true) {
            const newId = uuidv4();
            cy.add({
              group: "edges",
              data: {
                id: newId,
                _id: newId,
                source: sourcenode.id,
                target: target_id,
                label: "Subflow",
                decorator: true,
                source_workflow: responseJson.id,
              },
            })
          } else {
            console.log("Node not found: ", target_id)
          }

		  try { 
          	  cy.fit(null, 400);
		  } catch (e) {
			  console.log("Error in fitting (1): ", e)
		  }
          cy.on("add", "node", (e) => onNodeAdded(e));
          cy.on("add", "edge", (e) => onEdgeAdded(e));
        } else {
          if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id.length > 0 && responseJson.parentorg_workflow === "") {
            setOriginalWorkflow(responseJson)
          }

		  const tmpUi = new URLSearchParams(cursearch).get("ui");
		  if (
		    tmpUi !== undefined &&
		    tmpUi !== null &&
		    tmpUi === "yaml"
		  ) {
			  setTimeout(() => {
		  		setupWorkflowYaml(responseJson) 
			  }, 2500)
		  }

          setWorkflow(responseJson);
          setWorkflowDone(true);

          // Add error checks
          if (!responseJson.public) {
            if (
              !responseJson.previously_saved ||
              !responseJson.is_valid ||
              responseJson.errors !== undefined ||
              responseJson.errors !== null ||
              responseJson.errors !== // what
              responseJson.errors.length > 0
            ) {
              console.log("Setting configure Modal to open")
            }

            setConfigureWorkflowModalOpen(true)
          }



        }
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Get workflows error: ", error.toString());
      });
  };

  const onUnselect = (event) => {
    const nodedata = event.target.data();
    console.log("UNSELECT: ", nodedata);
    //if (nodedata.type === "ACTION") {
    //	setLastSelected(nodedata)
    //}
    //

    // Wait for new node to possibly be selected
    //setTimeout(() => {
    const typeIds = cy.elements('node:selected').jsons();
    for (var idkey in typeIds) {
      const item = typeIds[idkey]
      if (item.data.isButton === true) {
        //console.log("Reselect old node & return - or just return?")

        if (item.data.buttonType === "delete" && item.data.attachedTo === nodedata.id) {
          //console.log("delete of same node!")
        }
        return
      }
    }

    // Ensuring overwriting
    if (nodedata?.type === "ACTION") {

      if (nodedata?.parameters !== undefined && nodedata.parameters !== null && nodedata.parameters.length > 0 && workflow?.actions !== undefined && workflow?.actions !== null && workflow?.actions.length > 0) {
        for (var actionkey in workflow.actions) {
          const action = workflow.actions[actionkey]
          if (action.id === nodedata.id) {
            workflow.actions[actionkey].parameters = nodedata.parameters
            break
          }
        }
      }
    }

    // Unselecting all
    //cy.elements().unselect()

    //if (nodedata.app_name === undefined && nodedata.source === undefined) {
    //  return;
    //}
    //event.target.removeClass("selected");
    //





    //// If button is clicked, select current node

    // Attempt at rewrite of name in other actions in following nodes.
    // Should probably be done in the onBlur for the textfield instead
    /*
    if (event.target.data().type === "ACTION") {
      const nodeaction = event.target.data()
      const curaction = workflow.actions.find(a => a.id === nodeaction.id)
      console.log("workflowaction: ", curaction)
      console.log("nodeaction: ", nodeaction)
      if (nodeaction.label !== curaction.label) {
        console.log("BEACH!")

        var params = []
        const fixedName = "$"+curaction.label.toLowerCase().replace(" ", "_")
        for (var actionkey in workflow.actions) {
          if (workflow.actions[actionkey].id === curaction.id) {
            continue
          }

          for (var paramkey in workflow.actions[actionkey].parameters) {
            const param = workflow.actions[actionkey].parameters[paramkey]
            if (param.value === null || param.value === undefined || !param.value.includes("$")) {
              continue
            }

            const innername = param.value.toLowerCase().replace(" ", "_")
            if (innername.includes(fixedName)) {
              //workflow.actions[actionkey].parameters[paramkey].replace(
              //console.log("FOUND!: ", innername)
            }
          }
        }
      }
    }
    */


    // FIXME - check if they have value before overriding like this for no reason.
    // Would save a lot of time (400~ ms -> 30ms)
    //console.log("ACTION: ", selectedAction)
    //console.log("APP: ", selectedApp)

    //setSubworkflow({})
    ReactDOM.unstable_batchedUpdates(() => {
      setSelectedAction({});
      setSelectedApp({});
      setSelectedComment({})
      setSelectedEdge({})
      setTriggerAuthentication({})
      setLocalFirstrequest(true)

      setSelectedTrigger({});
      setSelectedTriggerIndex(-1)
      setSubworkflow({})
      setUpdate(Math.random())

      // Can be used for right side view
      setRightSideBarOpen(false);
      setScrollConfig({
        top: 0,
        left: 0,
        selected: "",
      });
    })

    sendStreamRequest({
      "item": "node",
      "type": "unselect",
      "id": workflow.id,
    })
    //}, 150)
  };

  const onEdgeSelect = (event) => {

      const selectedNodes = cy.$(':selected');
      if (selectedNodes && selectedNodes.length > 1) {
        event.target.unselect();
        return; 
      }

    ReactDOM.unstable_batchedUpdates(() => {
      setRightSideBarOpen(true);
      setLastSaved(false);

      /*
       // Used to not be able to edit trigger-based branches. 
        const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"])
        if (triggercheck === undefined) {
      */
      if (
        event.target.data("type") !== "COMMENT" &&
        event.target.data().decorator
      ) {
        toast("This edge can't be edited.");
      } else {
        const destinationId = event.target.data("target");
        const curaction = workflow.actions.find((a) => a.id === destinationId);
        //console.log("ACTION: ", curaction)
        if (curaction !== undefined && curaction !== null) {
          if (
            curaction.app_name === "Shuffle Tools" &&
            curaction.name === "router"
          ) {
            toast("Router action can't have incoming conditions");
            event.target.unselect();
            return;
          }
        }

        setSelectedEdgeIndex(
          workflow.branches.findIndex(
            (data) => data.id === event.target.data()["id"]
          )
        );
        setSelectedEdge(event.target.data());
      }

      setSelectedAction({});
      setSelectedTrigger({});
    })
  };

  // Comparing locations between nodes and setting views
  var styledElements = [];
  var originalLocation = {
    x: 0,
    y: 0,
  };

  const onCtxTap = (event) => {
    const nodedata = event.target.data();
    console.log(nodedata);
    if (nodedata.type === "TRIGGER" && (nodedata.app_name === "Shuffle Workflow" || nodedata.app_name === "User Input")) {

      if (nodedata.parameters === null) {
        toast("Set a workflow first");
        return;
      }

      if (nodedata.parameters === undefined) {
        return
      }

      const workflow_id = nodedata.parameters.find((param) => param.name === "workflow" || param.name === "subflow");

      if (workflow.id === workflow_id.valu) {
        return;
      }

      cy.animation({
        zoom: 0,
        center: {
          eles: event.target,
        },
      })
        .play()
        .promise()
        .then(() => {
          console.log("DONE: ", workflow_id);
          getWorkflow(workflow_id.value, nodedata);

		  try {
          	  cy.fit(null, 300);
		  } catch (e) {
			  console.log("Error in fitting (2): ", e)
		  }
        });
    }
  };

  const onNodeDragStop = (event, selectedAction) => {
    const nodedata = event.target.data()
    if (nodedata.id === selectedAction.id) {
      //console.log("Same node, return")
      return
    }

    if (nodedata.finished === false) {
      return
    }

    const connected = event.target.connectedEdges().jsons()
    if (connected.length > 0 && connected !== undefined) {
      for (let connectkey in connected) {
        const edge = connected[connectkey]
        if (edge.data.decorator && edge.data.label === releaseToConnectLabel) {
          // Transform to normal edge
          const currentedge = cy.getElementById(edge.data.id)
          if (currentedge !== undefined && currentedge !== null) {
            currentedge.data("decorator", false)
            currentedge.data("label", "")
          }
          continue
        }

        const sourcenode = cy.getElementById(edge.data.source)
        const destinationnode = cy.getElementById(edge.data.target)
        if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) {
          continue
        }

        const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position())
        const currentedge = cy.getElementById(edge.data.id)
        if (currentedge !== undefined && currentedge !== null) {
          currentedge.style('control-point-distance', edgeCurve.distance)
          currentedge.style('control-point-weight', edgeCurve.weight)
        }
      }
    }

    if (styledElements.length === 1) {
      console.log(
        "Should reset location and autofill: ",
        styledElements,
        selectedAction
      );
      if (originalLocation.x !== 0 || originalLocation.y !== 0) {
        const currentnode = cy.getElementById(nodedata.id);
        if (currentnode !== null && currentnode !== undefined) {
          currentnode.position("x", originalLocation.x);
          currentnode.position("y", originalLocation.y);
        }

        originalLocation = { x: 0, y: 0 };
      }

      const curElement = document.getElementById(styledElements[0]);
      if (curElement !== null && curElement !== undefined) {
        curElement.style.border = curElement.style.original_border;
        var newValue = "$" + nodedata.label.toLowerCase().replaceAll(" ", "_");
        if (nodedata.type === "TRIGGER") {
          if (nodedata.trigger_type === "WEBHOOK" || nodedata.trigger_type === "SCHEDULE" || nodedata.trigger_type === "EMAIL") {
            var newValue = "$exec"
          }
        }
        var paramname = "";
        var idnumber = -1;
        if (curElement.id.startsWith("rightside_field_")) {

          // Find exact position to put the text

          const idsplit = curElement.id.split("_");
          console.log(idsplit);
          if (idsplit.length === 3 && !isNaN(idsplit[2])) {

            selectedAction.parameters[idsplit[2]].value += newValue;
            paramname = selectedAction.parameters[idsplit[2]].name;
            idnumber = idsplit[2];
          }
        }

        if (idnumber >= 0 && paramname.length > 0) {
          const exampledata = GetExampleResult(nodedata);
          const parsedname = paramname
            .toLowerCase()
            .trim()
            .replaceAll("_", " ");

          const foundresult = GetParamMatch(parsedname, exampledata, "");
          if (foundresult.length > 0) {
            console.log("FOUND RESULT: ", paramname, foundresult);
            newValue = `${newValue}${foundresult}`;
          }

          selectedAction.parameters[idnumber].value = newValue;
        }

        curElement.value = newValue;
      }
    }

    if (
      nodedata.app_name !== undefined &&
      ((nodedata.app_name !== "Shuffle Tools" &&
        nodedata.app_name !== "Testing" &&
        nodedata.app_name !== "Shuffle Workflow" &&
        nodedata.app_name !== "Integration Framework" &&
        nodedata.app_name !== "Singul" &&
        nodedata.app_name !== "User Input") ||
        nodedata.isStartNode)
    ) {
      const allNodes = cy.nodes().jsons();
      var found = false;
      for (let nodekey in allNodes) {
        const currentNode = allNodes[nodekey];
        if (currentNode.data.attachedTo === nodedata.id && currentNode.data.isDescriptor) {
          found = true
          break
        }
      }

      if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule") {
        if (!found) {
          //console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
          // Find how many executions it has 
          var executions = 0
          const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
          const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
          const decoratorNode = {
            position: {
              x: event.target.position().x + 44,
              y: event.target.position().y + 44,
            },
            locked: true,
            data: {
              isDescriptor: true,
              isValid: true,
              is_valid: true,
              isTrigger: true,
              label: `${matchingExecutions.length}`,
              attachedTo: nodedata.id,
              imageColor: color,
              hasExecutions: true,
            },
          };

          cy.add(decoratorNode)
        }
      } else {
        // Readding the icon after moving the node
		/*
        if (!found) {
          const iconInfo = GetIconInfo(nodedata);
          const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
          const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);

          const offset = nodedata.isStartNode ? 36 : 44;
          const decoratorNode = {
            position: {
              x: event.target.position().x + offset,
              y: event.target.position().y + offset,
            },
            locked: true,
            data: {
              isDescriptor: true,
              isValid: true,
              is_valid: true,
              label: "",
              image: svgpin_Url,
              imageColor: iconInfo.iconBackgroundColor,
              attachedTo: nodedata.id,
            },
          };

          cy.add(decoratorNode).unselectify();
        } else {
          //console.log("Node already exists - don't add descriptor node");
        }
		*/
      }
    }

    originalLocation = {
      x: 0,
      y: 0,
    };

    sendStreamRequest({
      "item": "node",
      "type": "move",
      "id": nodedata.id,
      "location": {
        "x": event.target.position("x"),
        "y": event.target.position("y"),
      }
    })
  };

  // Check if it already has any non-decorator branches attached to it
  const findClosestNode = (event, nodedata) => {
	if (cy === undefined || cy === null) {
		console.log("Cy is undefined or null")
		return
	}

	if (event === undefined || event === null) {
		console.log("Event is undefined or null")
		return
	}

	if (event.target === undefined || event.target === null) {
		console.log("Event target is undefined or null")
		return
	}

	if (!((nodedata?.trigger_type === "SUBFLOW" || nodedata?.trigger_type === "USERINPUT" || nodedata?.type === "ACTION") && !nodedata?.isStartNode)) {
		//console.log("Not a valid node to find closest node for")

		return
	}

	if (nodedata.finished === false) {
		//console.log("Node is not finished")
		return
	}

	const branches = cy.elements('edge').jsons()
	var branchFound = false
	var decoratorNodeIds = []
	var decoratorIds = []
	for (var branchkey in branches) {
	  if (branches[branchkey].data.source === nodedata.id || branches[branchkey].data.target === nodedata.id) {
		decoratorIds.push(branches[branchkey].data.id)

		if (branches[branchkey].data.decorator === true) {

		  // Add the source/destination
		  if (branches[branchkey].data.source === nodedata.id) {
			decoratorNodeIds.push(branches[branchkey].data.target)
		  } else {
			decoratorNodeIds.push(branches[branchkey].data.source)
		  }

		  continue
		}

		//branchFound = true
		//break
	  }
	}

	if (!branchFound) {
	  var relevantNodes = [] 

	  //const minDistance = 185 
	  const minDistance = 85 
	  const draggedNode = event.target
	  const allnodes = cy.nodes().jsons()
	  for (var nodekey in allnodes) {
		const node = allnodes[nodekey]
		if (node.data.id === nodedata.id) {
		  continue
		}

		// Decorators
		if (node.data.attachedTo !== undefined) {
		  continue
		}

		if (node.position === undefined || node.position === null || node.position.x === undefined || node.position.y === undefined) {
		  continue
		}

		if (node.data.type !== "ACTION" && node.data.type !== "TRIGGER") {
		  continue
		}

		const distance = Math.sqrt(
		  Math.pow(draggedNode.position('x') - node.position.x, 2) +
		  Math.pow(draggedNode.position('y') - node.position.y, 2)
		)

		if (decoratorNodeIds.includes(node.data.id)) {

		  // Drag a little farther to remove it
		  if (distance > minDistance + 125) {
			// Remove the branch? Why?
			const edgeToRemove = cy.getElementById(branches[branchkey].data.id)
			if (edgeToRemove !== null && edgeToRemove !== undefined) {
			  //console.log("Removing edge: ", edgeToRemove)
			  edgeToRemove.remove()
			  break
			}
		  }
		}


		if (distance < minDistance) {
		  relevantNodes.push(node)
		  //minDistance = distance
		  //closestNode = node
		}
	  }

		for (var key in relevantNodes) {
			const closestNode = relevantNodes[key]
			if (closestNode.data.app_name === "Webhook" || closestNode.data.app_name === "Schedule") {
			  return
			}

			// Checks if the branch already exists between the nodes
			if (decoratorIds.length > 0) {
			  var foundBranch = false
			  for (var decoratorkey in decoratorIds) {
				const decoratorEdge = cy.getElementById(decoratorIds[decoratorkey])
				if (decoratorEdge === null || decoratorEdge === undefined) {
				  continue
				}

				// Check if source and destination exists with a branch
				const sourceId = decoratorEdge.data("source")
				const targetId = decoratorEdge.data("target")
				if ((sourceId  === closestNode.data.id && targetId === nodedata.id) || (sourceId === nodedata.id && targetId === closestNode.data.id)) { 
					foundBranch = true
					break
				}
			  }

			  if (foundBranch) {
				  continue
			  }
			}

		    const newId = uuidv4()
		    cy.add({
		      group: "edges",
		      data: {
		        decorator: true,
		        id: newId,
		        _id: newId,
		        source: closestNode.data.id,
		        target: nodedata.id,
		        label: releaseToConnectLabel,
		        conditions: [],
		      }
		    })
	  }
	}

	/* 
	// FIXME: This is the start of a highlighter for the node
	// to better match it up with other elements
	// 1. Get current node's position in X/Y on the screen
	// 2. Draw a red line on the X and Y axis for positioning
	
	// Draw a red div line in the HTML
	const position = event.target.position()
	const redline = document.getElementById("redline")
	if (redline !== null && redline !== undefined) {
	redline.style.display = "block"
	redline.style.position = "absolute"
	redline.style.left = position.x + "px"
	redline.style.top = position.y + "px"
	redline.style.height = "10000px"
	redline.style.width = 1
	console.log("REDLINE!")
	}
      	*/
	}

  const onNodeDrag = (event, selectedAction) => {
    const nodedata = event.target.data();

    if (nodedata.finished === false) {
      console.log("NOT FINISHED - ADD EXAMPLE BRANCHES TO CLOSEST!!")
      return
    }

    if (nodedata.app_name !== undefined) {
      const allNodes = cy.nodes().jsons();
      for (var nodekey in allNodes) {
        const currentNode = allNodes[nodekey];
        if (currentNode.data.attachedTo === nodedata.id) {
          cy.getElementById(currentNode.data.id).remove();
        }

        // Calculate location
        //currentNode.position.x > 
        //if (nodedata.position.x > 0 && nodedata.position.y > 0) {
        //	console.log("Positive both")
        //}

        //console.log(currentNode.position)
        //console.log(nodedata.position)
      }
    } else {
      //console.log("No appid? ", nodedata)
    }

    if (nodedata.buttonType === "edgehandler") {
      console.log("Enable edgehandler!")
      console.log("Find parent: ", nodedata.attachedTo)
      const parentNode = cy.getElementById(nodedata.attachedTo);
      if (parentNode !== null && parentNode !== undefined) {
        console.log("Start parentnode tracking!")
        //cy.edgehandles().start(parentNode)
      }
    }

    if (nodedata.id === selectedAction.id) {
      return;
    }

	// Finds closest partner to show edge to connect to 
    if ((nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT" || nodedata.type === "ACTION") && !nodedata.isStartNode) {
	  findClosestNode(event, nodedata)
    }

    if (originalLocation.x === 0 && originalLocation.y === 0 && nodedata.position !== undefined) {
      originalLocation.x = nodedata.position.x;
      originalLocation.y = nodedata.position.y;
    }

    // Part of autocomplete. Styles elements in frontend to indicate
    // what and where we may input data for the user.
    const onMouseUpdate = (e) => {
      const x = e.pageX;
      const y = e.pageY;

      const elementMouseIsOver = document.elementFromPoint(x, y);
      if (elementMouseIsOver !== undefined && elementMouseIsOver !== null) {
        // Color for #FF8544 translated to rgb
        const newBorder = "3px solid rgb(248, 90, 62)";
        if (
          elementMouseIsOver.style.border !== newBorder &&
          elementMouseIsOver.id.includes("rightside")
        ) {
          if (elementMouseIsOver.style.border !== undefined) {
            elementMouseIsOver.style.original_border =
              elementMouseIsOver.style.border;
          } else {
            elementMouseIsOver.style.original_border = "";
          }

          elementMouseIsOver.style.border = newBorder;
          console.log("STYLED: ", styledElements);
          for (var styledElementsKey in styledElements) {
            const curElement = document.getElementById(styledElements[styledElementsKey]);
            if (curElement !== null && curElement !== undefined) {
              curElement.style.border = curElement.style.original_border;
            }
          }

          styledElements = [];
          styledElements.push(elementMouseIsOver.id);
        } else if (
          elementMouseIsOver.id === "cytoscape_view" ||
          elementMouseIsOver.id === ""
        ) {
          for (var index in styledElements) {
            const curElement = document.getElementById(styledElements[index]);
            if (curElement !== null && curElement !== undefined) {
              curElement.style.border = curElement.style.original_border;
            }
          }

          styledElements = [];
        }
      }

      // Ensure it only happens once
      document.removeEventListener("mousemove", onMouseUpdate, false);
    };

    document.addEventListener("mousemove", onMouseUpdate, false);
  };


  useBeforeunload(() => {
    if (!lastSaved) {
      return unloadText;
    } else {
      if (workflow.public === false) {
        //document.removeEventListener("mousemove", onMouseUpdate, true);
        document.removeEventListener("keydown", handleKeyDown, true);
        document.removeEventListener("paste", handlePaste, true);
      }
    }
  });

  // Should get AI autocompletes
  const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => {
    if (setResponseMsg !== undefined) {
      setResponseMsg("")
    }

    if (value === undefined || value === "") {
      console.log("No value input!")
      return
    }

    if (setSuggestionLoading !== undefined) {
      setSuggestionLoading(true)
    }

    console.log("Submit conversation with value: ", value);

    // This is to find sample response and parse it as string

    var AppContext = []
    var originalParams = []
    var originalField = ""

    if (inputAction !== undefined && inputAction !== null) {
      // Reload the data without copying
      inputAction = JSON.parse(JSON.stringify(inputAction))
      originalParams = JSON.parse(JSON.stringify(inputAction.parameters))
      if (originalParams.length > 0) {
        originalField = originalParams[0].name
      }
      const parents = getParents(inputAction)

      var actionlist = []
      if (parents.length > 1) {
        for (let [key, keyval] in Object.entries(parents)) {
          const item = parents[key];
          if (item.label === "Runtime Argument") {
            continue;
          }

          var exampledata = item.example === undefined || item.example === null ? "" : item.example;
          // Find previous execution and their variables
          //exampledata === "" &&
          if (workflowExecutions.length > 0) {
            // Look for the ID
            const found = false;
            for (let [key, keyval] in Object.entries(workflowExecutions)) {
              if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
                continue;
              }

              var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id);
              if (foundResult === undefined || foundResult === null) {
                continue;
              }

              if (foundResult.result !== undefined && foundResult.result !== null) {
                foundResult = foundResult.result
              }

              const valid = validateJson(foundResult, true)
              if (valid.valid) {
                if (valid.result.success === false) {
                  //console.log("Skipping success false autocomplete")
                } else {
                  exampledata = valid.result;
                  break;
                }
              } else {
                exampledata = foundResult;
              }
            }
          }

          // 1. Take
          const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_");

          const actionvalue = {
            app_name: item.app_name,
            action_name: item.name,
            label: item.label,

            type: "action",
            id: item.id,
            name: item.label,
            autocomplete: itemlabelComplete,
            example: exampledata,
          }

		  console.log("VALUE: ", actionvalue)

          actionlist.push(actionvalue);
        }
      }

      var fixedResults = []
      for (var i = 0; i < actionlist.length; i++) {
        const item = actionlist[i];
        const responseFix = SetJsonDotnotation(item.example, "")

        // Check if json
        const validated = validateJson(responseFix)
        var exampledata = responseFix;
        if (validated.valid) {
          exampledata = JSON.stringify(validated.result)
        }

        AppContext.push({
          "app_name": item.app_name,
          "action_name": item.action_name,
          "label": item.label,
          "example": exampledata,
          //"example_response": exampledata,
        })
      }

      var params = []
      for (var paramkey in inputAction.parameters) {
        const param = inputAction.parameters[paramkey]
        if (param.configuration) {
          continue
        }

        // Mainly for booleans
        if (param.options !== undefined && param.options !== null && param.options.length > 0) {
          continue
        }

        params.push(param)
      }

      inputAction.parameters = params
    }

    var conversationData = {
      "query": value,
      "output_format": "action",
      "app_context": AppContext,

      "workflow_id": workflow.id,
    }


    if (inputAction !== undefined) {
      console.log("Add app context! This should them get parameters directly")
      conversationData.output_format = "action_parameters"

      conversationData.app_id = inputAction.app_id
      conversationData.app_name = inputAction.app_name
      conversationData.action_name = inputAction.name
      conversationData.parameters = inputAction.parameters
    }

    // Onprem not available yet (April 2023)
    // Should: Make OpenAI work for them with their own key
    //fetch(`${globalUrl}/api/v1/conversation`, {
    const url = `${globalUrl}/api/v1/conversation`
    fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(conversationData),
      credentials: "include",
    })
      .then((response) => {
        setAutocompleting(false)
        if (setSuggestionLoading !== undefined) {
          setSuggestionLoading(false)
        }

        if (response.status !== 200) {
          console.log("Status not 200 for stream results :O!");
        } else {
          toast("Completion finished. Please verify the output and run the workflow again!")
        }

        return response.json();
      })
      .then((responseJson) => {
        console.log("Conversation response: ", responseJson)
        if (responseJson.success === false) {
          if (responseJson.reason !== undefined) {
            if (setResponseMsg !== undefined) {
              setResponseMsg(responseJson.reason)
            }

            toast.error(responseJson.reason)
          }

          return
        } else {
          setAiQueryModalOpen(false)
        }

        if (inputAction !== undefined) {
          console.log("In input action! Should check params if they match, and add suggestions")

          if (responseJson.parameters === undefined || responseJson.parameters.length === 0) {
            return
          }

          var changed = false
          var codeeditorfound = false
          for (let respParamKey in responseJson.parameters) {
            var respParam = responseJson.parameters[respParamKey]
            if (respParam.value === undefined || respParam.value === null || respParam.value === "") {
              continue
            }

            for (var paramkey in selectedAction?.parameters) {
              const actionParam = selectedAction.parameters[paramkey]
              if (actionParam.name !== respParam.name) {
                continue
              }

              const codeeditor = document.getElementById("shuffle-codeeditor")
              if (codeeditor !== undefined && codeeditor !== null && actionParam.name === originalField) {
                const editorInstance = window?.ace?.edit("shuffle-codeeditor")
                if (editorInstance === undefined || editorInstance === null) {
                  toast.error("Failed to find code editor instance")
                  return
                } else {
                  codeeditorfound = true
                  editorInstance.setValue(respParam.value)
                  //selectedAction.parameters[paramkey].value = respParam.value
                  changed = true
                }
              }

              if (!changed) {
                console.log("Found match for param: ", respParam)
                changed = true
                selectedAction.parameters[paramkey].autocompleted = true
                selectedAction.parameters[paramkey].value = respParam.value
              }
            }
          }

          if (changed === true && codeeditorfound === false) {
            //inputAction.parameters = JSON.parse(JSON.stringify(selectedAction.parameters))
            selectedAction.parameters = JSON.parse(JSON.stringify(selectedAction.parameters))
            console.log("Setting action! Force update pls :)")
            setUpdate(Math.random())

            setSelectedAction(selectedAction)

            // Find it in cytoscape and update the action 
            if (cy !== undefined && cy !== null) {
              const cyAction = cy.getElementById(inputAction.id)
              if (cyAction !== undefined && cyAction !== null) {
                cyAction.data("parameters", selectedAction.parameters)
              }
            }

          }

          return
        }

        // Add action
        console.log("Suggestionbox location: ", suggestionBox)
        if (responseJson.app_name !== undefined && responseJson.app_name !== null) {
          // Always added to 0, 0
          // Should use suggestionBox.position.x, suggestionBox.position.y
          var newitem = {
            "data": responseJson,
            "position": {
              "x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0,
              "y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0,
            },
            "group": "nodes",
          }

          newitem.type = "ACTION"
          newitem.isStartNode = false
          newitem.data.id = uuidv4()
          newitem.data.type = "ACTION"
          newitem.data.isStartNode = false

          newitem.data.is_valid = true
          newitem.data.isValid = true

          cy.add({
            group: newitem.group,
            data: newitem.data,
            position: newitem.position,
          });

          // Add edge
          const newId = uuidv4()
          cy.add({
            group: "edges",
            data: {
              id: newId,
              _id: newId,
              source: suggestionBox.attachedTo,
              target: newitem.data.id,
            }
          })
          //label: "Generated",

          setSuggestionBox({
            "position": {
              "top": 500,
              "left": 500,
            },
            "open": false,
            "attachedTo": "",
          });
        }
      })
      .catch((error) => {
        setAiQueryModalOpen(false)
        setAutocompleting(false)
        if (setSuggestionLoading !== undefined) {
          setSuggestionLoading(false)
        }

        console.log("Conv response error: ", error);
      });
  }



  // Nodeselectbatching:
  // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
  // onNodeClick
  const onNodeSelect = (event, newAppAuth) => {

    // Forces all states to update at the same time,
    // Otherwise everything is SUPER slow

    // FIXME: Do absolutely NOT use JSON.stringify on the event.target.data()
    // This causes memory referencing to become a nightmare
    const data = event.target.data()
    if (data.app_name === "Shuffle Workflow") {
      if ((data?.parameters !== undefined) && (data?.parameters?.length > 0)) {
        getWorkflowApps(data.parameters[0].value)
      }
    }

    if (data.buttonType == "ACTIONSUGGESTION") {
      const attachedToId = data.attachedTo

      const parentitemRaw = cy.getElementById(data.attachedTo)
      const parentitem = parentitemRaw.data()
      if (parentitem !== null && parentitem !== undefined) {
        setTimeout(() => {
          parentitemRaw.select()

          const allNodes = cy.nodes().jsons()
          for (var _key in allNodes) {
            const currentNode = allNodes[_key]

            if (currentNode.data.buttonType === "ACTIONSUGGESTION") {
              cy.getElementById(currentNode.data.id).remove()
            }
          }
        }, 100)

        const findaction = data.label
        for (let appkey in apps) {
          const curapp = apps[appkey]
          if (curapp.name !== parentitem.app_name) {
            continue
          }

          if (curapp.actions === undefined || curapp.actions === null) {
            continue
          }

          for (let actionkey in curapp.actions) {
            const curaction = curapp.actions[actionkey]

            if (curaction.category_label !== undefined && curaction.category_label !== null && curaction.category_label.length > 0) {
              if (curaction.category_label[0].toLowerCase() === findaction.toLowerCase()) {
                console.log("FOUND: ", curaction)

                // Update the action itself
                // Find the action index, and update:
                // - label 
                // - description
                // - parameters
                // - name

                var foundindex = -1
                for (let wfactionkey in workflow.actions) {
                  const wfaction = workflow.actions[wfactionkey]
                  if (wfaction.id === data.attachedTo) {
                    foundindex = wfactionkey
                    break
                  }
                }

                console.log("Updating action: ", foundindex, findaction)
                if (foundindex >= 0) {
                  workflow.actions[foundindex].label = findaction
                  workflow.actions[foundindex].description = curaction.description
                  workflow.actions[foundindex].parameters = curaction.parameters
                  workflow.actions[foundindex].name = curaction.name

                  setWorkflow(workflow)
                  console.log(workflow)
                }
                break
              }
            }
          }

          break
        }

        return
      }

    } else if (data.isSuggestion === true) {
      console.log("Suggestion! Replace with a real action.")

      const attachedToId = data.attachedTo
      //event.target.data("attachedTo", "")

      const allNodes = cy.nodes().jsons();
      for (var _key in allNodes) {
        const currentNode = allNodes[_key];
        // console.log("CURRENT NODE: ", currentNode)
        if ((currentNode.data.isButton || currentNode.data.isSuggestion) && currentNode.data.attachedTo !== data.id) {
          cy.getElementById(currentNode.data.id).remove();
        }
      }


      // Add relevant fields for the action and connect it to the parent (attachedTo)
      //event.target.data("attachedTo", "")
      // Decides directionality
      //
      const isTarget = event.target.data("isTarget")
      const target = isTarget ? data.id : attachedToId
      const source = isTarget ? attachedToId : data.id
      const newId = uuidv4()

      // Add a new node
      const newAction = {
        ...data,
      }

      newAction.attachedTo = ""
      newAction.isSuggestion = false
      newAction.finished = true
      newAction.isButton = false
      newAction.private_id = ""
      newAction.type = "ACTION"
      if (newAction.app_name === "Shuffle Subflow") {
        newAction.type = "TRIGGER"
        newAction.trigger_type = "SUBFLOW"
      } else {
        newAction.decorator = false
        newAction.suggested = true
      }

      newAction.label = data.label
      newAction.id = uuidv4()
      // Find the app and add params?

      cy.add({
        group: "nodes",
        data: newAction,
        position: {
          x: event.target.position().x,
          y: event.target.position().y,
        },
      })

      toast("Suggestion added!")
      setTimeout(() => {
        const newBranch = {
          source: source,
          target: newAction.id,
          _id: newId,
          id: newId,
          decorator: false,
          finished: true,
        }

        cy.add({
          group: "edges",
          data: newBranch,
        })

        setWorkflowRecommendations(undefined)

        // Find the new node we added from newAction.id
        const newActionNode = cy.getElementById(newAction.id)
        if (newActionNode !== undefined && newActionNode !== null) {
          newActionNode.select()
        }

        aiSubmit("Fill based on previous values", undefined, undefined, newAction)
      }, 1000)
      return
    }

    ReactDOM.unstable_batchedUpdates(() => {
	  const selectedNodes = cy.$(':selected')
      if (data.isButton) {
		if (selectedNodes?.length > 1) {
			event.target.unselect()
			//console.log(": ", selectedNodes.length)
			return
		}

        if (data.buttonType === "suggestion") {
          if (cy === undefined) {
            console.log("Cy not defined yet")
            return
          }

          // Inject HTML at a fixed location?
          //const newHtml = "<div id='suggestion' style='position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: white; z-index: 1000;'><div style='position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);'><h1>Do you want to add this suggestion?</h1><button id='suggestion_yes'>Yes</button><button id='suggestion_no'>No</button></div></div>"

          // Find mouse cursor position on screen
          console.log("Suggestion html to be added at location: ", event)
          /*
          const position = {
            "top": cy.pan().y, 
            "left": cy.pan().x, 
          }
          */


          const position = event.target.renderedPosition();
          const container = cy.container();
          const offset = {
            left: container.offsetLeft,
            top: container.offsetTop
          };

          // Calculate the actual screen position for the box
          const screenPosition = {
            left: position.x + offset.left - 150,
            top: position.y + offset.top,
          };

          // Log the position to the console
          console.log('Node screen position:', screenPosition);

          const newbox = {
            "position": screenPosition,
            "node_position": event.target.position(),
            "open": true,
            "attachedTo": data.attachedTo,
          }

          console.log("Rendered position: ", newbox.node_position)

          setSuggestionBox(newbox)

          // Unselect
          event.target.unselect();

        } else if (data.buttonType === "delete") {
          const parentNode = cy.getElementById(data.attachedTo);
          if (parentNode !== null && parentNode !== undefined) {
            removeNode(data.attachedTo)
            //parentNode.remove()
          }

          return
        } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") {
		  // Find any node that has isStartNode set to true and set it to false	
		  const foundNodes = cy.nodes().jsons()
		  var relevantNodes = []
		  for (var nodekey in foundNodes) {
			const node = foundNodes[nodekey]
			if (node.data.isStartNode === true) {
				relevantNodes.push(node)
			}
		  }

          const parentNode = cy.getElementById(data.attachedTo)
          if (parentNode !== null && parentNode !== undefined) {
			for (var nodekey in relevantNodes) {
				const node = relevantNodes[nodekey]
				var oldstartnode = cy.getElementById(node.data.id);
				if (
				  oldstartnode !== null &&
				  oldstartnode !== undefined &&
				  oldstartnode.length > 0
				) {
				  try {
					console.log("Old startnodes: ", oldstartnode)
					for(var i = 0; i < oldstartnode.length; i++) {
						oldstartnode[i].data("isStartNode", false);
					}
				  } catch (e) {
					console.log("Startnode error: ", e);
				  }
				}
			}

            workflow.start = parentNode.data("id");
            setLastSaved(false);
            parentNode.data("isStartNode", true);
          }

          //event.target.unselect();
          setRightSideBarOpen(true);
          return

        } else if (data.buttonType === "copy") {

          // 1. Find parent
          // 2. Find branches for parent
          // 3. Make a new node that's moved a little bit
          const parentNode = cy.getElementById(data.attachedTo);
          if (parentNode !== null && parentNode !== undefined && parentNode.data() !== undefined && parentNode.data() !== null) {
            var newNodeData = JSON.parse(JSON.stringify(parentNode.data()));
            newNodeData.id = uuidv4();
            if (newNodeData.position !== undefined) {
              newNodeData.position = {
                x: newNodeData.position.x + 100,
                y: newNodeData.position.y + 100,
              }
            }

            newNodeData.isStartNode = false;
            newNodeData.errors = [];
            newNodeData.is_valid = true;
            newNodeData.isValid = true;
            newNodeData.label = parentNode.data("label") + "_copy";

            cy.add({
              group: "nodes",
              data: newNodeData,
              position: newNodeData.position,
            });

            // Readding the icon after moving the node
            if (
              newNodeData.app_name !== "Testing" ||
              newNodeData.app_name !== "Shuffle Workflow"
            ) {
            } else {
              const iconInfo = GetIconInfo(newNodeData);
              const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
              const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);

              const offset = newNodeData.isStartNode ? 36 : 44;
              const decoratorNode = {
                position: {
                  x: newNodeData.position.x + offset,
                  y: newNodeData.position.y + offset,
                },
                locked: true,
                data: {
                  isDescriptor: true,
                  isValid: true,
                  is_valid: true,
                  label: "",
                  image: svgpin_Url,
                  imageColor: iconInfo.iconBackgroundColor,
                  attachedTo: newNodeData.id,
                },
              };

              cy.add(decoratorNode).unselectify();
            }

            workflow.actions.push(newNodeData);

            const sourcebranches = workflow.branches.filter((foundbranch) => foundbranch.source_id === parentNode.data("id"))


            const destinationbranches = workflow.branches.filter((foundbranch) => foundbranch.destination_id === parentNode.data("id"))


            for (var sourceBranchesKey in sourcebranches) {
              var newbranch = JSON.parse(JSON.stringify(sourcebranches[sourceBranchesKey]));

              newbranch.id = uuidv4()
              newbranch.source_id = newNodeData.id

              newbranch._id = newbranch.id
              newbranch.source = newbranch.source_id
              newbranch.target = newbranch.destination_id
              cy.add({
                group: "edges",
                data: newbranch,
              })
            }

            for (var destinationBranchesKey in destinationbranches) {
              var newbranch = JSON.parse(JSON.stringify(destinationbranches[destinationBranchesKey]))

              const sourcenode = cy.getElementById(newbranch.source_id)
              if (sourcenode !== null && sourcenode !== undefined) {
                const sourcedata = sourcenode.data()

                if (sourcedata.trigger_type !== "SUBFLOW" && sourcedata.trigger_type !== "USERINPUT") {
                  continue
                }

              }

              newbranch.id = uuidv4()
              newbranch.destination_id = newNodeData.id

              newbranch._id = newbranch.id
              newbranch.source = newbranch.source_id
              newbranch.target = newbranch.destination_id
              cy.add({
                group: "edges",
                data: newbranch,
              })
            }

            //event.target.unselect();
            return
          }
        }

        return;
      } else if (data.isDescriptor) {
        // Find parent
        event.target.unselect();

        if (data.attachedTo !== undefined && data.attachedTo !== null && data.attachedTo.length > 0) {
          const parentNode = cy.getElementById(data.attachedTo)
          if (parentNode !== null && parentNode !== undefined) {
            setTimeout(() => {
              parentNode.select()
            }, 100)
          }
        }

        //console.log("Can't select descriptor");
        if (data.isTrigger) {
          console.log("But maybe we can select trigger descriptor? Maybe open execution tab?")
          setExecutionModalOpen(true)
        }

        return;
      }

      if (data.type === undefined) {
        console.log("No type, automatically setting to action");
        data.type = "ACTION"
      }

      if (data.type === "ACTION") {
		if (selectedNodes?.length > 1) {
			console.log("Unselecting ACTION due to multiple nodes selected")
			setSelectedAction({})
			setSelectedApp({})
			setSelectedComment({})
			return
		}

        setSelectedComment({})

        // FIXME: is this what is mapping it an actual action in the workflow? wtf?
        var curactionIndex = workflow.actions.findIndex((a) => a.id === data.id)
        var curaction = undefined
        if (curactionIndex >= 0) {
          curaction = workflow.actions[curactionIndex]
        }

        if (!curaction || curaction === undefined) {
          if (data.id !== undefined && data.app_name !== undefined) {
            workflow.actions.push(data)
            setWorkflow(workflow)

            // FIXME: Is this necessary?
            //curaction = JSON.parse(JSON.stringify(data))
          } else {
            if (workflow.public !== true) {
              toast("Action not found. Please remake it.");
            }

            event.target.remove()
            return
          }
        }

        // FIXME: This change may have caused... something
        // FIXME: Somehow there is a referencing problem between the action in 
        // cytoscape and the one in the "workflow.actions" state
        curaction = data
        //workflow.actions[curactionIndex] = curaction

        //const data = event.target.data()
        //event.target.data(curaction)
        //event.target.data(curaction)

        var newapps = apps
        if (apps === null || apps === undefined || apps.length === 0) {
          newapps = filteredApps
        }

        // Check ID first, then names etc
        // That way it always selects the right IF it exists
        var curapp = newapps.find((a) =>
          a.id === curaction.app_id
        )

        if (curapp === undefined || curapp === null) {
          console.log(`Couldn't find app with ID '${curaction.app_id}' - checking with name & version`)

          curapp = newapps.find((a) =>
            a.name === curaction.app_name &&
            (a.app_version === curaction.app_version ||
              (a.loop_versions !== null &&
                a.loop_versions.includes(curaction.app_version)))
          )
        }

        if (curapp === undefined || curapp === null) {
          curapp = integrationApps.find((a) =>
            a.name === curaction.app_name &&
            (a.app_version === curaction.app_version ||
              (a.loop_versions !== null &&
                a.loop_versions.includes(curaction.app_version)))
          )
        }

        if (curaction.template === true && curaction.name !== undefined) {
          //newapps.
          const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase()

          curaction.matching_actions = []
          for (var newAppskey in newapps) {
            for (let actionsSubkey in newapps[newAppskey].actions) {
              const tmpaction = newapps[newAppskey].actions[actionsSubkey]
              if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) {
                console.log("MATCH!: ", newapps[newAppskey])
                curaction.matching_actions.push({
                  "app_name": newapps[newAppskey].name,
                  "app_version": newapps[newAppskey].app_version,
                  "app_id": newapps[newAppskey].id,
                  "action": tmpaction,
                  "large_image": newapps[newAppskey].large_image,
                  "app_index": newAppskey,
                  "action_index": actionsSubkey,
                })
              }
            }
          }
        }

        if (!curapp || curapp === undefined) {
          // Check local storage has it 
          const foundapps = localStorage.getItem("apps")
          if (foundapps !== null && foundapps !== undefined) {
            try {
              const parsedapps = JSON.parse(foundapps)
              if (parsedapps !== null && parsedapps !== undefined && parsedapps.length > 0) {
                for (let appkey in parsedapps) {
                  if (parsedapps[appkey].name === curaction.app_name) {
                    curapp = parsedapps[appkey]
                    break
                  }
                }
              }
            } catch (e) {
              console.log("Problem with parsing apps from local storage", e)
            }

          } else {
            console.log("No apps found in local storage")
          }
        }

        /*
        if (curapp && curapp.app_id !== undefined && curapp.app_id !== null && curapp.app_id.length > 0 &&curapp.actions.length <= 1) {
          toast(`Side-loading app ${curapp.name} to get actions.`)
        }
        */

        if (curapp !== undefined && curapp !== null && curapp.id !== undefined && curapp.id !== null && curapp.id.length > 0) {
          loadAppConfig(curapp.id, true)
        }

        if (!curapp || curapp === undefined) {
          const tmpapp = {
            name: curaction.app_name,
            app_name: curaction.app_name,
            app_version: curaction.app_version,
            id: curaction.app_id,
            actions: [curaction],
          }

          setSelectedApp(tmpapp)
          setSelectedAction(curaction)
        } else {

          curaction.app_id = curapp.id

          if (curapp.authentication === undefined || curapp.authentication === null) {
            setAuthenticationType({
              type: "",
            })

            curapp.authentication = {
              type: "",
              required: false,
            }
          } else {
            setAuthenticationType(
              curapp.authentication.type === "oauth2-app" || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null) ? {
                type: curapp.authentication.type,
                redirect_uri: curapp.authentication.redirect_uri,
                refresh_uri: curapp.authentication.refresh_uri,
                token_uri: curapp.authentication.token_uri,
                scope: curapp.authentication.scope,
                client_id: curapp.authentication.client_id,
                client_secret: curapp.authentication.client_secret,
                grant_type: curapp.authentication.grant_type,
              } : {
                type: "",
              }
            )
          }

          var requiresAuth = curapp?.authentication?.required
		  if (curaction.app_id === "integration" || curaction.app_id === "shuffle_agent") { 

			requiresAuth = false
			for (var paramkey in curaction.parameters) {
				const param = curaction.parameters[paramkey]
				if (param.name === "app_name" && param?.value?.length > 0) {

					const foundapp = apps.find((a) => a.name === param.value)
					if (foundapp !== undefined && foundapp !== null && foundapp?.authentication?.required === true) { 
						requiresAuth = true
						for (var key in appAuthentication) {
							if (appAuthentication[key]?.app?.name === foundapp?.name) {
								requiresAuth = false
								break
							}
						}
					}

					break
				}
			}
		  }

          setRequiresAuthentication(requiresAuth)
          if (curapp.authentication.required) {
            //console.log("App requires auth.")
            // Setup auth here :)
            const authenticationOptions = [];
            var findAuthId = "";
            if (
              curaction.authentication_id !== null &&
              curaction.authentication_id !== undefined &&
              curaction.authentication_id.length > 0
            ) {
              findAuthId = curaction.authentication_id;
            }

            const tmpAuth = JSON.parse(JSON.stringify(newAppAuth));

            const curappName = curapp.name.toLowerCase()
            for (let tmpAuthKey in tmpAuth) {
              var item = tmpAuth[tmpAuthKey];

              const newfields = {};
              if (item.app.name.toLowerCase() !== curappName) {
                continue
              }

              // Makes list into key:value object 
              for (let fieldkey in item.fields) {
                if (item.fields[fieldkey] === undefined) {
                  console.log("Problem with filterkey in Node select", fieldkey)
                  continue
                }

                const filterkey = item.fields[fieldkey]["key"]
                if (filterkey === null || filterkey === undefined) {
                  console.log("Problem with filterkey 2. Null or undefined 3")
                  continue
                }

                newfields[filterkey] = item.fields[fieldkey]["value"];
              }

              item.fields = newfields;
              if (item.app.name.toLowerCase() === curappName) {
                authenticationOptions.push(item);
                if (item.id === findAuthId) {
                  curaction.selectedAuthentication = item;
                }
              }
            }

            // Find with authenticationOption (authenticationOptions) has the highest .edited time. In this index, set the "last_modified" to true

            var latesttime = 0
            var latestindex = -1

            for (var i = 0; i < authenticationOptions.length; i++) {
              const authopt = authenticationOptions[i]

              if (authopt.edited > latesttime) {
                latesttime = authopt.edited
                latestindex = i
              }
            }

            if (latestindex !== -1) {
              authenticationOptions[latestindex].last_modified = true
            }

            curaction.authentication = authenticationOptions
            if (
              curaction.selectedAuthentication === null ||
              curaction.selectedAuthentication === undefined ||
              curaction.selectedAuthentication.length === ""
            ) {
              curaction.selectedAuthentication = {};
            }
          } else {

            curaction.authentication = []
            curaction.authentication_id = "";
            curaction.selectedAuthentication = {};
          }

          if (
            curaction.parameters !== undefined &&
            curaction.parameters !== null &&
            curaction.parameters.length > 0
          ) {
            for (var curActionParamKey in curaction.parameters) {
              if (
                curaction.parameters[curActionParamKey].options !== undefined &&
                curaction.parameters[curActionParamKey].options !== null &&
                curaction.parameters[curActionParamKey].options.length > 0 &&
                curaction.parameters[curActionParamKey].value === ""
              ) {
                curaction.parameters[curActionParamKey].value = curaction.parameters[curActionParamKey].options[0];
              }
            }

          } else {
            console.log("Should check APP if it has the same params as ACTION")
            for (let actionKey in curapp.actions) {
              const tmpaction = curapp.actions[actionKey]
              if (tmpaction.name === curaction.name) {
                console.log("Found action - needs change?", tmpaction)
                if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) {
                  curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters))
                }
                break
              }
            }

          }

          // Fix authentication fields that may be missing in the UI
          if (curapp.authentication.required && !curapp?.authentication?.type?.includes("oauth")) {
            if (curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) {
              var actionChanged = false
              for (let paramKey in curapp.authentication.parameters) {
                var param = curapp.authentication.parameters[paramKey]

                if (curaction.parameters === undefined || curaction.parameters === null) {
                  curaction.parameters = []
                }

                var found = false
                for (let actionParamKey in curaction.parameters) {
                  if (curaction.parameters[actionParamKey].name === param.name) {
                    found = true
                    break
                  }
                }

                if (!found) {
                  param.configuration = true
                  curaction.parameters.push(param)

                  actionChanged = true
                }
              }

              if (actionChanged && workflow.actions !== undefined && workflow.actions !== null) {
                // Find it in the workflow and set it
                for (let wfActionKey in workflow.actions) {
                  if (workflow.actions[wfActionKey].id === curaction.id) {
                    workflow.actions[wfActionKey] = curaction
                  }
                }

                setWorkflow(workflow)

              }
            }
          }

		  setTimeout(() => {
			  setSelectedApp(curapp)
			  setSelectedAction(curaction)
		  }, 50)

          cy.removeListener("drag");
          cy.removeListener("free");
          cy.on("drag", "node", (e) => {
            const node = e.target;

            updateNodeAndNeighborsOrientation(node);

            onNodeDrag(e, curaction);
          });

          cy.on("free", "node", (e) => {
            const node = e.target;
            updateNodeAndNeighborsOrientation(node);

            onNodeDragStop(e, curaction);
          });
        }

        if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) {
          var parsedenv = environments
          //if (typeof environments === "object") {
          //	parsedenv = [environments]
          //}

          const envs = parsedenv.find((a) => a.Name === curaction.environment);
          var env = environments[defaultEnvironmentIndex]
          if (envs !== undefined && envs !== null) {
            env = envs
          }

          setSelectedActionEnvironment(env);
        }
      } else if (data.type === "TRIGGER") {
		if (selectedNodes?.length > 1) {
			console.log("Unselecting ACTION due to multiple nodes selected")
			setSelectedAction({})
			setSelectedApp({})
			setSelectedComment({})
			return
		}

        setSelectedComment({})
        if (workflow.triggers === null) {
          workflow.triggers = []
        }

        var trigger_index = workflow.triggers.findIndex(
          (a) => a.id === data.id
        )

        if (trigger_index === -1) {

		  // Don't do this in suborg workflows.
		  if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null) {
			  toast("RE-adding missing trigger node onclick")
			  workflow.triggers.push(data)
			  trigger_index = workflow.triggers.length - 1
			  setWorkflow(workflow)
		  }

		  for (var triggerkey in workflow.triggers) {
			  const curtrigger = workflow.triggers[triggerkey]
			  if (curtrigger.name === data.name) {
				  trigger_index = triggerkey
				  break
			  }
		  }
        }

        if (data.app_name === "Shuffle Workflow" || data.app_name === "User Input") {

          // Check if public workflow
          if (workflow.public === true) {
            setWorkflows([workflow])
          } else {
            getAvailableWorkflows(trigger_index);
          }
        } else if (data.app_name === "Schedule") {
			if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0 && originalWorkflow.org_id !== undefined && originalWorkflow.org_id !== null && originalWorkflow.org_id.length > 0 && workflow.org_id === originalWorkflow.org_id) {
				// Allows a parent workflow to control the schedule
			} else if (data.replacement_for_trigger !== undefined && data.replacement_for_trigger !== null && data.replacement_for_trigger.length > 0) {

				// No custom control on cloud 
				if (isCloud) {
					toast.warning("This schedule is controlled by the parent workflow. If you want additional schedule control, please add a custom schedule to this workflow.", {
						autoClose: 30000,
					})
					event.target.unselect()
					return
				}
			}

        } else if (data.app_name === "Webhook" && trigger_index >= 0) {
		  if (workflow.triggers[trigger_index] !== undefined && workflow.triggers[trigger_index] !== null &&
			  (	workflow.triggers[trigger_index].parameters === undefined || 
				workflow.triggers[trigger_index].parameters === null || 
				workflow.triggers[trigger_index].parameters.length === 0)
		  	  ) {
			  workflow.triggers[trigger_index].parameters = [
				{
				  name: "url",
				  value: referenceUrl + "webhook_" + selectedTrigger.id,
				},
				{
				  name: "tmp",
				  value: "webhook_" + selectedTrigger.id,
				},
				{
				  name: "auth_headers",
				  value: "",
				},
				{
				  name: "custom_response_body",
				  value: "",
				},
				{
				  name: "await_response",
				  value: "v1",
				},
			  ]
		  }

          if (workflow.triggers[trigger_index].parameters !== undefined && workflow.triggers[trigger_index].parameters !== null && workflow.triggers[trigger_index].parameters.length > 0) {

            workflow.triggers[trigger_index].parameters[0] = {
              name: "url",
              value: referenceUrl + "webhook_" + workflow.triggers[trigger_index].id,
            };

            if (workflow.triggers[trigger_index].parameters.length < 5) {
              console.log("Adding to webhook params!")
              workflow.triggers[trigger_index].parameters.push({
                name: "await_response",
                value: "v1,"
              })
            }

            //workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined &&
          }
        } else if (data.app_name === "Pipeline" && trigger_index >= 0) {

          // Check if environment is set
          if (data.environment === undefined || data.environment === null || data.environment === "" || data.environment.toLowerCase() === "cloud") {
            for (var envKey in environments) {
              if (environments[envKey].archived === true) {
                continue
              }

              if (environments[envKey].Name.toLowerCase() === "cloud") {
                continue
              }

              workflow.triggers[trigger_index].environment = environments[envKey].Name
              data.environment = environments[envKey].Name
              //setSelectedTrigger(data)
              break
            }
          }
        }

        setTimeout(() => {
          if (trigger_index !== -1) {
            const trigger = workflow.triggers[trigger_index]
            if (trigger !== undefined && trigger !== null) {

              // Autofixer
              if (trigger.trigger_type === "USERINPUT") {
                const relevantparams = [
                  "alertinfo",
                  "options",
                  "type",
                  "email",
                  "sms",
                  "subflow",
                ]
                var foundparams = 0
                for (var paramkey in trigger.parameters) {
                  if (relevantparams.includes(trigger.parameters[paramkey].name)) {
                    foundparams++
                  }
                }

                if (foundparams < 6) {
                  trigger.parameters = [{
                    name: "alertinfo",
                    value: "Do you want to continue the workflow? Start parameters: $exec",
                  }, {
                    name: "options",
                    value: "boolean",
                  },
                  {
                    name: "type",
                    value: "subflow",
                  },
                  {
                    name: "email",
                    value: "test@test.com",
                  },
                  {
                    name: "sms",
                    value: "0000000",
                  },
                  {
                    name: "subflow",
                    value: "",
                  }]

                  workflow.triggers[trigger_index].parameters = trigger.parameters
                }
              }
            }
          }

          if (allTriggers !== undefined && allTriggers !== null) {

            // Just checking all three. Could just make a new list, but meh 
            if (allTriggers.pipelines !== undefined && allTriggers.pipelines !== null) {
              for (var pipelineKey in allTriggers.pipelines) {
                if (allTriggers.pipelines[pipelineKey].id === data.id) {
                  data.status = allTriggers.pipelines[pipelineKey].status
                }
              }
            }

            if (allTriggers.webhooks !== undefined && allTriggers.webhooks !== null) {
              for (var webhookKey in allTriggers.webhooks) {
                if (allTriggers.webhooks[webhookKey].id === data.id) {
                  data.status = allTriggers.webhooks[webhookKey].status
                }
              }
            }

            if (allTriggers.schedules !== undefined && allTriggers.schedules !== null) {
              for (var scheduleKey in allTriggers.schedules) {
                if (allTriggers.schedules[scheduleKey].id === data.id) {
                  data.status = allTriggers.schedules[scheduleKey].status
                }
              }
            }
          }

          setSelectedTriggerIndex(trigger_index)
          setSelectedTrigger(data)
          //setSelectedActionEnvironment(data.env)
        }, 25)
      } else if (data.type === "COMMENT") {
		if (selectedNodes?.length > 1) {
			console.log("Unselecting ACTION due to multiple nodes selected")
			setSelectedAction({})
			setSelectedApp({})
			setSelectedComment({})
			return
		}

        setSelectedComment(data);
      } else if (data.type === "RESIZE-HANDLE") {

        const parentNode = cy.getElementById(data.attachedTo);
        if (parentNode) {
          console.log("Resizing parent node:", parentNode);

          // Get the parent node's data
          const parentData = parentNode.data();
          if (parentData?.type === "COMMENT") {

            // Set the parent node's data as the selected comment
            setSelectedComment(parentData);
          }
        }
        return; // Exit after handling the resize handle
      } else {
        toast("Can't handle node type " + data.type);
        return;
      }

      setRightSideBarOpen(true);
      //setLastSaved(false);
      setScrollConfig({
        top: 0,
        left: 0,
        selected: "",
      });

      setSuggestionBox({
        "position": {
          "top": 500,
          "left": 500,
        },
        "open": false,
        "attachedTo": "",
      });

      sendStreamRequest({
        "item": "node",
        "type": "select",
        "id": data.id,
        "location": {
          "x": event.target.position("x"),
          "y": event.target.position("y"),
        }
      })

    })
  }

  const activateApp = (appid, refresh) => {
    fetch(`${globalUrl}/api/v1/apps/${appid}/activate`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Failed to activate")
        }

        return response.json()
      })
      .then((responseJson) => {
        if (responseJson.success === false) {
			if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
          		toast.error("Failed to auto-activate the app: " + responseJson.reason)
			} else {
          		toast.error("Failed to auto-activate the app. Go to /apps and activate it.")
			}
        } else {
          if (refresh === true) {
            setHighlightedApp(appid)
            //toast("App activated for your organisation! Refresh the page to use the app.")
            getApps()

          }
        }
      })
      .catch(error => {
        //toast(error.toString())
        console.log("Activate app error: ", error.toString())
      });
  }

  const GetExampleResult = (item) => {
    var exampledata = item.example === undefined ? "" : item.example;
    if (workflowExecutions.length > 0) {
      // Look for the ID
      for (let execkey in workflowExecutions) {
        if (
          workflowExecutions[execkey].results === undefined ||
          workflowExecutions[execkey].results === null
        ) {
          continue;
        }

        var foundResult = { result: "" };
        if (item.id === "exec") {
          if (
            workflowExecutions[execkey].execution_argument !== undefined &&
            workflowExecutions[execkey].execution_argument !== null &&
            workflowExecutions[execkey].execution_argument.length > 0
          ) {
            foundResult.result = workflowExecutions[execkey].execution_argument;
          } else {
            continue;
          }
        } else {
          foundResult = workflowExecutions[execkey].results.find(
            (result) => result.action.id === item.id
          );
          if (foundResult === undefined) {
            continue;
          }
        }

        foundResult.result = foundResult.result.trim();
        foundResult.result = foundResult.result.split(" None").join(' "None"');
        foundResult.result = foundResult.result.split(" False").join(" false");
        foundResult.result = foundResult.result.split(" True").join(" true");

        var jsonvalid = true;
        try {
          if (
            !foundResult.result.includes("{") &&
            !foundResult.result.includes("[")
          ) {
            jsonvalid = false;
          }
        } catch (e) {
          try {
            foundResult.result = foundResult.result.split("'").join('"');
            if (
              !foundResult.result.includes("{") &&
              !foundResult.result.includes("[")
            ) {
              jsonvalid = false;
            }
          } catch (e) {
            jsonvalid = false;
          }
        }

        // Finds the FIRST json only
        if (jsonvalid) {
          try {
            exampledata = JSON.parse(foundResult.result)
          } catch (e) {
            console.log("Result: ", exampledata)

          }

          break;
        }
      }
    }

    return exampledata;
  };

  const GetParamMatch = (paramname, exampledata, basekey) => {
    if (typeof exampledata !== "object") {
      return "";
    }

    if (exampledata === null) {
      return "";
    }

    //console.log("NOT REPLACING ON PURPOSE!!")
    //return ""

    // Basically just a stupid if-else :)
    const synonyms = {
      id: [
        "id",
        "ref",
        "sourceref",
        "reference",
        "sourcereference",
        "alert id",
        "case id",
        "incident id",
        "service id",
        "sid",
        "uid",
        "uuid",
        "team id",
        "message id",
        "message_id",
      ],
      title: ["title", "name", "message"],
      description: ["description", "explanation", "story", "details"],
      email: ["mail", "email", "sender", "receiver", "recipient"],
      data: [
        "data",
        "ip",
        "domain",
        "url",
        "hash",
        "md5",
        "sha2",
        "sha256",
        "value",
        "item",
      ],
      tags: ["tags", "taxonomies"],
    };

    // 1. Find the right synonym
    // 2. Replace with an autocomplete if it exists
    var selectedsynonyms = [paramname];
    for (const [key, value] of Object.entries(synonyms)) {
      if (key === paramname || value.includes(paramname)) {
        if (!value.includes(key)) {
          value.push(key.toLowerCase());
        }

        selectedsynonyms = value;
        break;
      }
    }

    var toreturn = "";

    for (const [key, value] of Object.entries(exampledata)) {
      // Check if loop or JSON

      if (typeof value === "object") {
        if (Array.isArray(value)) {
          var selectedkey = "";
          if (isNaN(key)) {
            selectedkey = `.${key}`;
          }

          for (let [subitem, subitemval] in Object.entries(value)) {
            toreturn = GetParamMatch(
              paramname,
              value[subitem],
              `${basekey}${selectedkey}.#`
            );
            if (toreturn.length > 0) {
              break;
            }
          }

          if (toreturn.length > 0) {
            break;
          }

        } else {
          var selectedkey = "";
          if (isNaN(key)) {
            selectedkey = `.${key}`;
          }

          toreturn = GetParamMatch(
            paramname,
            value,
            `${basekey}${selectedkey}`
          );
          if (toreturn.length > 0) {
            break;
          }
        }
      } else {
        if (selectedsynonyms.includes(key.toLowerCase())) {
          toreturn = `${basekey}.${key}`
          break
        }
      }
    }

    return toreturn;
  };

  // Takes an action as input, then runs through and updates the relevant fields
  // based on previous actions'
  // Uses lots of synonyms 
  // autocomplete
  const RunAutocompleter = (dstdata) => {
    // **PS: The right action should already be set here**
    // 1. Check execution argument
    // 2. Check parents in order
    var exampledata = GetExampleResult({ id: "exec", name: "exec" });
    var parentlabel = "exec";
    for (var dstdataParamKey in dstdata.parameters) {
      const param = dstdata.parameters[dstdataParamKey];
      // Skip authentication params
      if (param.configuration) {
        continue
      }

      if (param.options !== undefined && param.options !== null && param.options.length > 0) {
        continue
      }

      const paramname = param.name.toLowerCase().trim().replaceAll("_", " ");

      const foundresult = GetParamMatch(paramname, exampledata, "");
      if (foundresult.length > 0) {
        if (dstdata.parameters[dstdataParamKey].value.length === 0) {
          dstdata.parameters[dstdataParamKey].value = `$${parentlabel}${foundresult}`;
          dstdata.parameters[dstdataParamKey].autocompleted = true
        }
      }
    }

    var parents = getParents(dstdata);
    if (parents.length > 1) {
      for (let parentkey in parents) {
        const item = parents[parentkey];
        if (item.label === "Runtime Argument") {
          continue;
        }

        parentlabel =
          item.label === undefined
            ? ""
            : item.label.toLowerCase().trim().replaceAll(" ", "_");

        exampledata = GetExampleResult(item);
        if (dstdata.parameters !== undefined && dstdata.parameters !== null) {
          for (let [paramkey, paramkeyval] in Object.entries(dstdata.parameters)) {
            const param = dstdata.parameters[paramkey];
            // Skip authentication params
            if (param.configuration) {
              continue
            }

            if (param.options !== undefined && param.options !== null && param.options.length > 0) {
              continue
            }

            const paramname = param.name
              .toLowerCase()
              .trim()
              .replaceAll("_", " ");

            const foundresult = GetParamMatch(paramname, exampledata, "");
            if (foundresult.length > 0) {
              if (dstdata.parameters[paramkey].value.length === 0) {
                dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`;
                dstdata.parameters[paramkey].autocompleted = true
              } else {
                //dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`;
              }
            }
          }
        }
      }
    }

    return dstdata;
  };

  // Dynamic Label Positioning - Core Logic Function
  // This looks at all edges connected to a node and figures out if the workflow
  // is flowing horizontally (left-to-right) or vertically (top-to-bottom)
  // Based on that, it sets the node's "flowOrientation" which controls where the label appears
  const updateNodeLabelOrientation = (node) => {
    
    if (!node || typeof node.position !== "function") {
      return 
    }

    // Get all edges (connections) attached to this node
    const edges = node.connectedEdges()
    
    // If node has no edges, default to horizontal layout (label at bottom)
    if (!edges || edges.length === 0) {
      node.data("flowOrientation", "horizontal")  // Set the orientation data
      return
    }

    // Get the current position of this node (x, y coordinates)
    const nodePos = node.position()

    let hasHorizontalOutgoing = false 
    let hasStrongVertical = false      

    // Loop through each edge connected to this node
    edges.forEach((edge) => {
      // Check if this node is the source (start) of the edge
      const isSource = edge.source().id() === node.id()
      // Get the other node connected by this edge (target if we're source, source if we're target)
      const other = isSource ? edge.target() : edge.source()

      if (!other || typeof other.position !== "function") {
        return 
      }

      const otherPos = other.position()
      // Calculating the distance between the nodes
      const dx = Math.abs(otherPos.x - nodePos.x) 
      const dy = Math.abs(otherPos.y - nodePos.y) 

      // Ignore edges that are too small
      if (dx < 2 && dy < 2) {
        return 
      }

      // Determine if edge is mostly vertical (going up/down)
      const isVerticalish = dx < 30 && dy > 30   
      
      // Determine if edge is mostly horizontal (going left/right)
      const isHorizontalish = dy < 30 && dx > 30 

      if (isSource && isHorizontalish) {
        hasHorizontalOutgoing = true  
      }

      if (isVerticalish) {
        hasStrongVertical = true
      }
    })

    // Decision: Choose the orientation based on what we found
    // Priority order:
    // 1) If we have a horizontal OUTGOING edge → stay horizontal (label at bottom)
    //    (This is the default workflow style - left to right)
    // 2) Else if we have any strong vertical edge → go vertical (label on right)
    //    (This prevents labels from being cut by vertical edges)
    // 3) Else → default to horizontal (label at bottom)
    let newOrientation = "horizontal" 
    if (!hasHorizontalOutgoing && hasStrongVertical) {
      newOrientation = "vertical"  
    }

    // Save the orientation to the node's data
    // This will trigger the CSS rules we defined in defaultCytoscapeStyle.jsx
    node.data("flowOrientation", newOrientation)
  }

  // This updates node orientation and its connected neighbors
  const updateNodeAndNeighborsOrientation = (node) => {
    updateNodeLabelOrientation(node);
    
    const edges = node.connectedEdges();
    if (edges && edges.length > 0) {
      edges.forEach((edge) => {
        const source = edge.source();
        const target = edge.target();
        
        if (source && source.id() !== node.id()) {
          updateNodeLabelOrientation(source);
        }
        
        if (target && target.id() !== node.id()) {
          updateNodeLabelOrientation(target);
        }
      });
    }
  };

  // Multi-Node Dragging
  const handleMultiDrag = (e) => {

    const node = e.target         
    const ev = e.originalEvent 

    if (!ev || !ev.ctrlKey) {
      return;
    }

    const start = node.data("multiDragStartPos")

    if (!start) {
      const selectedNodes = cy.$(":selected")

      selectedNodes.forEach((n) => {
        n.data("multiDragStartPos", { ...n.position() })
      })
      return
    }

    // SUBSEQUENT DRAG EVENTS: Move all selected nodes together
    // Calculate how far the dragged node has moved
    const cur = node.position()    
    const dx = cur.x - start.x      
    const dy = cur.y - start.y

    // Get all selected nodes again
    const selectedNodes = cy.$(":selected")
    
    // Move each selected node by the same distance (dx, dy)
    selectedNodes.forEach((n) => {
      const s = n.data("multiDragStartPos") 
      
      // Skip if no starting position saved, or if this is the node being dragged
      // (the dragged node is already being moved by Cytoscape automatically)
      if (!s || n.id() === node.id()) {
        return
      }

      // Move this node to: original position + how far the dragged node moved
      n.position({
        x: s.x + dx,  // Original x + horizontal movement
        y: s.y + dy,  // Original y + vertical movement
      })
    })
  }

  // Checks for errors in edges when they're added
  const onEdgeAdded = (event) => {
    setLastSaved(false);
    const edge = event.target.data();

    if (edge.source === undefined && edge.target === undefined) {
      //console.log("Edge added without source or target")

      return
    }

    if (edge.readded === true) {
      console.log("Readded edge - stopping")

      event.target.data("readded", false)
      return
    }

    const sourcenode = cy.getElementById(edge.source)
    const destinationnode = cy.getElementById(edge.target)

    if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) {
      console.log("Source or destination node is undefined or null: ", sourcenode, destinationnode)
    } else {
      updateNodeLabelOrientation(sourcenode)
      updateNodeLabelOrientation(destinationnode)
      if (sourcenode.data("name") === "switch") {
        event.target.remove()
        return
      }

      //console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data())
      if (sourcenode.data("type") === "TRIGGER") {
        if (sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") {
          setTimeout(() => {
            const alledges = cy.edges().jsons()
            var targetedge = alledges.findIndex(
              (data) => data.data.source === edge.source && data.data.id !== edge.id
            )

            if (targetedge !== -1) {
              event.target.remove()

              //console.log("Found branch already!")
              toast.error("Triggers can point to exactly one start node")
              return


              // name: "Shuffle Workflow",
              // name: "User Input",
            } else {
              console.log("Node doesn't already have one")
            }
          }, 50)
        }
      }

      const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position())
      const currentedge = cy.getElementById(edge.id)
      if (currentedge !== undefined && currentedge !== null) {
        currentedge.style('control-point-distance', edgeCurve.distance)
        currentedge.style('control-point-weight', edgeCurve.weight)
      }
    }


    var targetnode = workflow.triggers.findIndex(
      (data) => data.id === edge.target
    )
    if (targetnode !== -1) {
      if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow" || workflow.triggers[targetnode].app_name === "Shuffle Subflow") {
        //console.log("User Input or Shuffle Workflow")
      } else {
        toast("Can't have triggers as target of branch")
        event.target.remove()
      }
    }

    const eventTarget = event.target.target()
    if (eventTarget.data("isButton") === true) {
      const parentNode = cy.getElementById(eventTarget.data("attachedTo"))
      event.target.remove()
      console.log("Setting it to parentnode: ", parentNode.data())
      if (parentNode !== undefined && parentNode !== null) {
        //event.target.data("target", eventTarget.data("attachedTo"))

        const newEdgeUuid = uuidv4()
        const newcybranch = {
          source: event.target.data("source"),
          target: eventTarget.data("attachedTo"),
          _id: newEdgeUuid,
          id: newEdgeUuid,
          hasErrors: event.target.data("hasErrors"),
        };

        const edgeToBeAdded = {
          group: "edges",
          data: newcybranch,
        }

        cy.add(edgeToBeAdded);
      }
    }

    if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") {
      console.log("Removing because of descriptor or comment")
      event.target.remove()
      return
    }

    targetnode = -1;

    // Check if:
    // dest == source && source == dest
    // dest == dest && source == source
    // backend: check all children? to stop recursion
    //
    var found = false;
    for (let branchkey in workflow.branches) {
      if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) {

		// Find the branch as well
		const foundbranch = cy.getElementById(workflow.branches[branchkey].id)
		if (foundbranch !== undefined && foundbranch !== null && foundbranch.data() !== undefined && foundbranch.data() !== null) {
			toast("A branch in the opposite direction already exists")
			event.target.remove()
			found = true
			break
		} 
      }

      if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) {

        console.log("That branch already exists: ", workflow.branches[branchkey])
        const foundbranch = cy.getElementById(workflow.branches[branchkey].id)
        if (foundbranch !== undefined && foundbranch !== null && foundbranch.data() !== undefined && foundbranch.data() !== null) {
          console.log("Removing branch: ", foundbranch.data())

          event.target.remove()

          found = true
          break
        } else {
          //console.log("Old branch didn't exist afterall. Remove.")
        }
      }

      if (edge.target === workflow.start) {
        targetnode = workflow.triggers.findIndex(
          (data) => data.id === edge.source
        )

        if (targetnode === -1) {
          if (targetnode.type !== "TRIGGER") {
			  console.log("SOURCENODE: ", sourcenode.data())
			  if (sourcenode.data("type") === "TRIGGER" && sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") {
			  } else {
				toast("Can't make branch to starting node");
				event.target.remove()
				break
			  }
          }

          found = true;
        }
      }

      if (edge.source === workflow.branches[branchkey].source_id) {
        // FIXME: Verify multi-target for triggers
        // 1. Check if destination exists
        // 2. Check if source is a trigger
        // targetnode = workflow.triggers.findIndex(data => data.id === edge.source)
        // console.log("Destination: ", edge.target)
        // console.log("CHECK SOURCE IF ITS A TRIGGER: ", targetnode)
        // if (targetnode !== -1) {
        // 	toast("Triggers can only target one target (startnode)")
        // 	event.target.remove()
        // 	found = true
        // 	break
        // }
      } else {
        //console.log("INSIDE LAST CHECK: ", edge)
        // Find the targetnode and check if its a trigger
        // FIXME - do this for both actions and other types?
        /*
        targetnode = workflow.triggers.findIndex(data => data.id === edge.target)
        if (targetnode !== -1) {
          if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow") {
          } else {
            toast("Can't have triggers as target of branch")
            event.target.remove()
            found = true
            break
          }
        } 
        */
      }
    }

    // 1. Guess what the next node's action should be
    // 2. Get result from previous nodes (if any)
    // 3. TRY to automatically map them in based on synonyms
    const newsource = cy.getElementById(edge.source);
    const newdst = cy.getElementById(edge.target);
    if (
      newsource !== undefined &&
      newsource !== null &&
      newdst !== undefined &&
      newdst !== null
    ) {
      const dstdata = RunAutocompleter(newdst.data());
      //console.log("DST Autocompleter: ", dstdata);
    }

    var newbranch = {
      source_id: edge.source,
      destination_id: edge.target,
      id_: edge.id,
      id: edge.id,
      hasErrors: false,
      decorator: false,
    };

    if (!found) {
      newbranch["hasErrors"] = false;

      workflow.branches.push(newbranch);
      setWorkflow(workflow);
    }

    history.push({
      type: "edge",
      action: "added",
      data: edge,
    });
    setHistory(history);
    setHistoryIndex(history.length);
  };

  const onNodeAdded = (event) => {
    const node = event.target;
    const nodedata = JSON.parse(JSON.stringify(event.target.data()))

    // Default to horizontal layout for new nodes if not explicitly set
    if (node.data("flowOrientation") === undefined) {
      node.data("flowOrientation", "horizontal");
    }

    if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined)) {
      return
    }

    // DONT MOVE THIS LINE RIGHT HERE v
    setLastSaved(false)
    if (node.isNode() && cy.nodes().size() === 1) {
      workflow.start = node.data("id")
      nodedata.isStartNode = true
    } else {
      if (workflow.actions === null) {
        console.log("Returning because node has no value")
        return
      }

      // Remove bad startnode
      for (let actionkey in workflow.actions) {
        const action = workflow.actions[actionkey];
        if (action.isStartNode && workflow.start !== action.id) {
          action.isStartNode = false
        }
      }
    }

    if (nodedata.app_id == "3e320a20966d33c9b7e6790b2705f0bf") {
      setWorkflowAsCode(true);
    }

    if (nodedata.decorator !== true && nodedata.attachedTo === undefined) {
      var newdata = JSON.parse(JSON.stringify(nodedata))
      newdata.large_image = ""
      sendStreamRequest({
        "item": "node",
        "type": "add",
        "id": nodedata.id,
        "data": nodedata,
        "x": node.position("x"),
        "y": node.position("y"),
      })
    }

    if (nodedata.type === "ACTION") {
      // Should get recommendations to load in for all nodesma

      if (workflow.actions.length === 1 && workflow.actions[0].id === workflow.start) {
        const newEdgeUuid = uuidv4();
        const newcybranch = {
          source: workflow.start,
          target: nodedata.id,
          _id: newEdgeUuid,
          id: newEdgeUuid,
          hasErrors: false,
        };

        const edgeToBeAdded = {
          group: "edges",
          data: newcybranch,
        };

        cy.add(edgeToBeAdded);
      }

      if (nodedata.app_name === "Shuffle Tools") {
        const iconInfo = GetIconInfo(nodedata);
        const iconViewBox = iconInfo.svgViewBox || `0 0 ${svgSize} ${svgSize}`;
        const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="${iconViewBox}" version="1.1" xmlns="http://www.w3.org/2000/svg">${buildIconSvgPath(iconInfo, "white")}</svg>`;
        const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
        nodedata.large_image = svgpin_Url;
        nodedata.fillGradient = iconInfo.fillGradient;
        nodedata.fillstyle = "solid";
        if (
          nodedata.fillGradient !== undefined &&
          nodedata.fillGradient !== null &&
          nodedata.fillGradient.length > 0
        ) {
          nodedata.fillstyle = "linear-gradient";
        } else {
          nodedata.iconBackground = iconInfo.iconBackgroundColor;
        }
      }


      if (nodedata.parameters !== undefined && nodedata.parameters !== null && !nodedata?.label?.endsWith("_copy")) {
        var newparameters = [];

        for (let [subkey, subkeyval] in Object.entries(nodedata.parameters)) {
          var newparam = JSON.parse(JSON.stringify(nodedata.parameters[subkey]))
          newparam.id = uuidv4()

          if (newparam.value === undefined || newparam.value === null) {
            newparam.value = ""
          } else {
            newparam.value = newparam.value
          }

          newparameters.push(newparam);
        }

        nodedata.parameters = newparameters
      }

      if (workflow.actions === undefined || workflow.actions === null) {
        workflow.actions = [nodedata];
      } else {
        workflow.actions.push(nodedata);
      }

      // 1. Check how many actions there are. If less than three, send a toast notification with suggested workflows 
      //if (workflow.actions.length < 3) {
      //    toast("Recommendations to show??")
      //}

      setWorkflow(workflow);
      fetchRecommendations(workflow)
    } else if (nodedata.type === "TRIGGER") {
      if (nodedata.is_valid === false) {
        toast("This trigger is not available to you");
        node.remove();
        return;
      }

      if (workflow.triggers === undefined) {
        workflow.triggers = [nodedata];
      } else {
        workflow.triggers.push(nodedata);
      }

      const newEdgeUuid = uuidv4();
      const newcybranch = {
        source: nodedata.id,
        target: workflow.start,
        source_id: nodedata.id,
        destination_id: workflow.start,
        _id: newEdgeUuid,
        id: newEdgeUuid,
        hasErrors: false,
        decorator: false,
      };

      const edgeToBeAdded = {
        group: "edges",
        data: newcybranch,
      };

      if (edgeToBeAdded.data.source !== edgeToBeAdded.data.target && edgeToBeAdded.data.source !== undefined && edgeToBeAdded.data.target !== undefined) {
        if (nodedata.name !== "User Input" && nodedata.name !== "Shuffle Workflow") {
          if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
            cy.add(edgeToBeAdded)
          }
        }
      }

      setWorkflow(workflow);
    }

    if (nodedata.app_name !== undefined) {
      history.push({
        type: "node",
        action: "added",
        data: nodedata,
      });
      setHistory(history);
      setHistoryIndex(history.length);
    }

  };

  const onEdgeRemoved = (event) => {
    setLastSaved(false);

    const edge = event.target;
    if (edge.data("decorator") === true) {
      return;
    }

    // Check if the source is trigger and can start
    //console.log("Removed: ", edge.data())
    const allNodes = cy.nodes().jsons()
    for (let nodekey in allNodes) {
      const curnode = allNodes[nodekey]
      if (curnode.data.type !== "TRIGGER") {
        continue
      }

      if (curnode.data.id === edge.data("source")) {
        console.log("Found matching trigger source: ", curnode)
        if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") {


          // If it's started, READD the edge
          if (curnode.data.status === "running") {
            //console.log("Edge is running - readd it: ", edge.data())

            // Just making sure it's not running infinitely
            var newdata = edge.data()
            newdata.readded = true

            try {
              cy.add({
                group: "edges",
                data: newdata,
              })

              //toast.error("You must STOP the trigger before deleting its branches")
              console.log("You must STOP the trigger before deleting its branches")
            } catch (e) {
              console.log("Failed re-adding edge: ", e)
            }
          }

          //status: "uninitialized",
        }
      }
    }

    workflow.branches = workflow.branches.filter(
      (a) => a.id !== edge.data().id
    );

    setWorkflow(workflow);
    event.target.remove();

    // trigger as source check
    const indexcheck = workflow.triggers.findIndex(
      (data) => edge.data("source") === data.id
    );
    if (indexcheck !== -1) {
      console.log("Shouldnt remove edge from trigger? ");
    }

    if (edge.data().source !== undefined) {
      history.push({
        type: "edge",
        action: "removed",
        data: edge.data(),
      });

      setHistory(history);
      setHistoryIndex(history.length);
    }
  };

  const onNodeRemoved = (event) => {
    const node = event.target;
    const data = node.data();

    // FIXME: This is still a bit buggy
    if (data.decorator !== true && data.attachedTo === undefined) {
      sendStreamRequest({
        "item": "node",
        "type": "remove",
        "id": data.id,
      })
    }

    if (data.finished === false) {
      return
    }


    workflow.actions = workflow.actions.filter((a) => a.id !== data.id);
    workflow.triggers = workflow.triggers.filter((a) => a.id !== data.id);
    if (workflow.start === data.id && workflow.actions.length > 0) {
      // FIXME - should check branches connected to startnode, as picking random
      // is just confusing
      if (workflow.actions[0].id !== data.id) {
        const ele = cy.getElementById(workflow.actions[0].id);
        if (ele !== undefined && ele !== null) {
          ele.data("isStartNode", true);
          workflow.start = ele.id();
        }
      } else {
        if (workflow.actions.length > 1) {
          const ele = cy.getElementById(workflow.actions[1].id);
          if (ele !== undefined && ele !== null) {
            ele.data("isStartNode", true);
            workflow.start = ele.id();
          }
        }
      }
    }

    if (data.app_name !== undefined) {
      const allNodes = cy.nodes().jsons();
      for (let allNodesKey in allNodes) {
        const currentNode = allNodes[allNodesKey];
        if (currentNode.data.attachedTo === data.id) {
          cy.getElementById(currentNode.data.id).remove();
        }
      }

      history.push({
        type: "node",
        action: "removed",
        data: data,
      })

      //console.log("REMOVED: ", data)
      setHistory(history);
      setHistoryIndex(history.length);
    }

    setWorkflow(workflow);
  }

   // Check if user is typing in an input field, textarea, or contenteditable element
   var isInputField = function(target) {
    if (!target) return false
    const tagName = target.localName || target.tagName?.toLowerCase()
    const isInput = tagName === "input" || tagName === "textarea"
    const isContentEditable = target.isContentEditable === true || target.contentEditable === "true"
    const isEditable = target.getAttribute && (target.getAttribute("contenteditable") === "true" || target.getAttribute("contenteditable") === "")
    return isInput || isContentEditable || isEditable
  }

  //var previouskey = 0
  const handleKeyDown = (event) => {

    // Check if any modal or sidebar is open - if so, disable custom shortcuts
    var isAnyModalOrSidebarOpen =
      variablesModalOpen ||
      aiQueryModalOpen ||
      workflowGenerationModalOpen ||
      executionVariablesModalOpen ||
      authenticationModalOpen ||
      conditionsModalOpen ||
      authgroupModalOpen ||
      codeModalOpen ||
      executionModalOpen ||
      editWorkflowModalOpen ||
      tenzirConfigModalOpen ||
      codeEditorModalOpen ||
      executionArgumentModalOpen ||
      searchModalOpen ||
      rightSideBarOpen ||
      leftSideBarOpenByClick ||
      selectionOpen ||
      (suggestionBox && suggestionBox.open)

    switch (event.keyCode) {
      case 27:
        if (configureWorkflowModalOpen === true) {
          setConfigureWorkflowModalOpen(false);
        }
        break;
      case 46:
        console.log("DELETE");
        break;
      case 38:
        //console.log("UP");
        break;
      case 37:
        //console.log("LEFT");
        break;
      case 40:
        //console.log("DOWN");
        break;
      case 39:
        //console.log("RIGHT");
        break;
      case 90:
        if (event.ctrlKey) {
          console.log("CTRL+Z");
        }

        break;
      // FEATURE: Multi-Node Copy (Cmd+C / Ctrl+C)
      case 67:
        if ((event.ctrlKey || event.metaKey) && !event.shiftKey) {

          // If any modal/sidebar is open, let browser handle normal copy
          if (isAnyModalOrSidebarOpen) {
            return
          }

          // Do not enter if it's an input field, textarea, or contenteditable element
          // (we don't want to interfere with normal text copying)
          if (isInputField(event.target) || isInputField(document.activeElement)) {
            return;
          }

          // Make sure Cytoscape is initialized
          if (cy !== undefined && cy !== null) {

            // Get all selected nodes (this includes decorator nodes, buttons, nodes etc.)
            const allSelected = cy.$(":selected").filter("node");
            
            // Filter to get only actual workflow nodes (not UI elements)
            const selectedNodes = allSelected.filter(node => {
              const data = node.data();
              // Only copy nodes that are:
              // - Not decorator nodes (small icon nodes attached to main nodes)
              // - Not descriptor nodes (UI elements)
              // - Not button nodes (delete/copy buttons)
              // - Either ACTION or TRIGGER type (actual workflow nodes)
              return data.decorator !== true && 
                     data.isDescriptor !== true && 
                     data.isButton !== true &&
                     (data.type === "ACTION" || data.type === "TRIGGER");
            });

            // Only proceed if we have valid nodes to copy
            if (selectedNodes.length > 0) {

              const nodeIds = selectedNodes.map(n => n.id());
              
              // Prepare node data for clipboard
              // We save: node data, position, and original ID (for remapping later)
              const nodesData = selectedNodes.map(n => ({
                data: n.data(),         
                position: n.position(),
                originalId: n.id()
              }));

              const internalBranches = workflow.branches.filter(branch =>
                nodeIds.includes(branch.source_id) &&      // Source node is selected
                nodeIds.includes(branch.destination_id)    // Destination node is selected
              );

              const clipboardData = {
                type: "copy_nodes_shuffle",
                nodes: nodesData,
                branches: internalBranches
              };

              // Write to clipboard as JSON string
              navigator.clipboard.writeText(JSON.stringify(clipboardData))
                .then(() => toast(`Copied ${selectedNodes.length} node(s) with ${internalBranches.length} branch(es)`))
                .catch(() => toast("Failed to copy to clipboard"));
              
              // Prevent browser's default copy behavior
              event.preventDefault();
            }
          }
        }
        break;
      // FEATURE: Multi-Node Paste (Cmd+V / Ctrl+V)
      case 86:
        // Check if Ctrl (Windows/Linux) or Cmd (Mac) is pressed
        if (event.ctrlKey || event.metaKey) {

          // If any modal/sidebar is open, let browser handle normal paste
          if (isAnyModalOrSidebarOpen) {
            return
          }

          // Don't paste if it's an input field, textarea, or contenteditable element
          if (isInputField(event.target) || isInputField(document.activeElement)) {
            return;
          }

          // Make sure Cytoscape is initialized
          if (cy !== undefined && cy !== null) {
            handlePaste(event);
          }
          // Prevent browser's default paste behavior
          event.preventDefault();
        }
        break;
      case 88:
        if (event.ctrlKey) {
          console.log("CTRL+X");
        }
        break;
      case 83:
        break;
      case 70:
        break;
      case 65:
        // As a poweruser myself, I found myself hitting this a few
        // too many times to just edit text. Need a better bind, which does NOT work while inside a field
        break;
      // FEATURE: Multi-Selection Delete (Delete / Backspace keys)
      // Key code 46 = Delete key (Mac/Windows)
      // Key code 8 = Backspace key (Windows)
      case 46: // Delete key
      case 8:  // Backspace key
        // If any modal/sidebar is open, let browser handle normal delete
        if (isAnyModalOrSidebarOpen) {
          return
        }

        // Don't delete if user is typing in an input field, textarea, or contenteditable element
        // (we don't want to interfere with normal text deletion)
        if (isInputField(event.target) || isInputField(document.activeElement)) {
          return;  // Exit early - let the browser handle normal deletion
        }

        // Make sure Cytoscape is initialized
        if (cy !== undefined && cy !== null) {
          // Get all selected nodes
          const selectedNodes = cy.$(":selected").filter("node");
          
          // Only proceed if we have nodes selected
          if (selectedNodes.length > 0) {
            // IMPORTANT: Collect node IDs first before deleting
            // This prevents issues if the selection changes while we're deleting
            const nodeIdsToDelete = [];
            
            // Loop through selected nodes and collect their IDs
            selectedNodes.forEach(node => {
              const nodeData = node.data();
              // Skip decorator nodes (small icon nodes) - they shouldn't be deleted directly
              // Exception: COMMENT type decorators can be deleted
              if (nodeData.decorator === true && nodeData.type !== "COMMENT") {
                return;  // Skip this node
              }
              // Add this node's ID to our delete list
              nodeIdsToDelete.push(nodeData.id);
            });

            // Delete each node using the existing removeNode function
            // This function handles proper cleanup (triggers, workflow updates, history, etc.)
            nodeIdsToDelete.forEach(nodeId => {
              removeNode(nodeId);  // This will trigger onNodeRemoved and clean up properly
            });

            // Prevent browser's default delete behavior
            event.preventDefault();
          }
        }
        break;
      default:
        break;
    }

    //previouskey = event.keyCode
  };

  const handlePaste = async (event) => {
    if (event.path !== undefined && event.path !== null && event.path.length > 0) {
      if (event.path[0].localName !== "body") {
        return;
      }
    }

    if (event.target !== undefined && event.target !== null) {
      if (event.target.localName === "input" || event.target.localName === "textarea") {
        return;
      }
    }

    if (cy === undefined || cy === null) return;

    try {
      let clipboardText;
      if (event.clipboardData) {
        clipboardText = event.clipboardData.getData("text/plain");
      } else {
        clipboardText = await navigator.clipboard.readText();
      }

      const clipboardData = JSON.parse(clipboardText);

      // Handle new multi-node copy format - use same logic as individual copy button
      if (clipboardData.type === "copy_nodes_shuffle" && clipboardData.nodes && clipboardData.branches) {
        const idMap = {};
        clipboardData.nodes.forEach(node => {
          idMap[node.originalId] = uuidv4();
        });

        clipboardData.nodes.forEach(node => {
          const newNodeData = JSON.parse(JSON.stringify(node.data));
          const newId = idMap[node.originalId];
          newNodeData.id = newId;
          if (newNodeData.position !== undefined) {
            newNodeData.position = {
              x: node.position.x + 450,
              y: node.position.y + 450,
            }
          }

          newNodeData.isStartNode = false;
          newNodeData.errors = [];
          newNodeData.is_valid = true;
          newNodeData.isValid = true;
          newNodeData.label = newNodeData.label + "_copy";

          cy.add({
            group: "nodes",
            data: newNodeData,
            position: newNodeData.position,
          });

          workflow.actions.push(newNodeData);
        });

        // Copy internal branches (between pasted nodes only - no connections to old nodes)
        clipboardData.branches.forEach(branch => {
          const newbranch = JSON.parse(JSON.stringify(branch));
          newbranch.id = uuidv4();
          newbranch.source_id = idMap[branch.source_id];
          newbranch.destination_id = idMap[branch.destination_id];
          newbranch._id = newbranch.id;
          newbranch.source = newbranch.source_id;
          newbranch.target = newbranch.destination_id;
          cy.add({
            group: "edges",
            data: newbranch,
          });
        });

        // Clear clipboard after successful paste
        navigator.clipboard.writeText("").catch(() => {});

        event.preventDefault();
        return;
      }

      // Fallback to old format for backward compatibility
      const allnodes = cy.nodes().jsons();
      var parsedjson = Array.isArray(clipboardData) ? clipboardData : [clipboardData];

      for (let jsonkey in parsedjson) {
        var item = parsedjson[jsonkey];

        if (item.data === undefined || item.data === null) {
          console.log("Appending from here")
          const newitem = {
            "data": item,
            "position": {
              "x": 0,
              "y": 0
            },
            "group": "nodes",
          }

          item = newitem
          item.type = "ACTION"
          item.isStartNode = false
          item.data.type = "ACTION"
          item.data.isStartNode = false
        }

		// Find a cy.data() label with the same name
		const foundnodes = allnodes.filter((data) => {
			//console.log("COMP: ", data.data.label, item.data.label)
			if (data.data.label === undefined || data.data.label === null) {
				return false
			}

			return data.data.label === item.data.label
		})

		if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) {
			// Weird naming copy lol
			item.data.label = item.data.label + "_copy_" + allnodes.length
		}

        item.data.id = uuidv4()

        cy.add({
          group: item.group,
          data: item.data,
          position: {
            x: (item.position?.x || 0) + 20,
            y: (item.position?.y || 0) + 20,
          },
        });
      }
    } catch (e) {
      console.log("Error pasting: ", e);
      //toast("Failed parsing clipboard: ", e)
    }
  };

  const registerKeys = () => {
    document.addEventListener("keydown", handleKeyDown);
    document.addEventListener("paste", handlePaste);
  };

  const getEnvironments = (orgId, defaultEnvironmentName) => {
    var headers = {
      "Content-Type": "application/json",
      "Accept": "application/json",
    }

    if (orgId !== undefined && orgId !== null && orgId.length > 0) {
      headers["Org-Id"] = orgId
    }

    fetch(globalUrl + "/api/v1/getenvironments", {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {

          console.log("Status not 200 for envs :O!");
          if (isCloud) {
            setEnvironments([{ Name: "Cloud", Type: "cloud" }]);
          } else {
            setEnvironments([{ Name: "Onprem", Type: "onprem" }]);
          }

          return;
        }

        return response.json();
      })
      .then((responseJson) => {
        var found = false
        var showEnvCnt = 0
        for (let jsonkey in responseJson) {
          if (responseJson[jsonkey].default && !found) {
            setDefaultEnvironmentIndex(jsonkey)
            found = true
          }

          if (responseJson[jsonkey].archived === false) {
            showEnvCnt += 1
          }
        }

        // Always showing for now
        //if (showEnvCnt > 1) {
        if (showEnvCnt > 0) {
          setShowEnvironment(true)
        }

        if (!found) {
          for (let jsonkey in responseJson) {
            if (!responseJson[jsonkey].archived) {
              setDefaultEnvironmentIndex(jsonkey)
              break
            }
          }
        }

        if (isCloud) {
          if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) {
            setEnvironments(responseJson)
          } else {
            setEnvironments([{ Name: "Cloud", Type: "cloud" }])
          }
        } else {
          setEnvironments(responseJson)
        }

		if (defaultEnvironmentName !== undefined && defaultEnvironmentName !== null && defaultEnvironmentName.length > 0 && responseJson !== undefined && responseJson !== null && responseJson.length > 0) {
			const env = responseJson.findIndex((data) => data.Name === defaultEnvironmentName)
			if (env !== -1) {
			  setSelectedActionEnvironment(responseJson[env])

			  if (originalSelectedEnvironment === undefined || originalSelectedEnvironment === null || Object.keys(originalSelectedEnvironment).length === 0) {
				  setOriginalSelectedEnvironment(responseJson[env])
			  }
			}
		}
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Get environments error: ", error.toString());
      });
  };

  if (
    !firstrequest &&
    graphSetup &&
    established &&
    props.match.params.key !== workflow.id &&
    workflow.id !== undefined &&
    workflow.id !== null &&
    workflow.id.length > 0
  ) {

    // Check if 
    if (distributedFromParent === "" && suborgWorkflows === []) {
      toast.info("Redirecting as the workflow ID does not match the URL")

      setTimeout(() => {
        window.location.pathname = "/workflows/" + props.match.params.key;
      }, 2500)
    }
  }

  const animationDuration = 150;
  const onNodeHoverOut = (event) => {
    const nodedata = event.target.data();

    const cytoscapeElement = document.getElementById("cytoscape_view")
    if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
      cytoscapeElement.style.cursor = "default"
    }

    if (nodedata.finished === false) {

      // Should just be 1, so this should be fast enough :3
      const incomingEdges = event.target.incomers("edge").jsons()
      if (incomingEdges !== undefined && incomingEdges !== null) {
        for (var i = 0; i < incomingEdges.length; i++) {
          // Find the actual edge
          const edge = cy.getElementById(incomingEdges[i].data.id)
          if (edge === undefined || edge === null) {
            console.log("edge is null or undefined")
            continue
          }

          // Set the edge to be dashed
          edge.style("target-arrow-color", "#555555")
          edge.style("line-style", "dashed")
          edge.style("line-gradient-stop-colors", ["#555555", "#555555"])
        }
      }

      return
    }

    if (nodedata.name === "switch") {
      return
    }

    // console.log("nodedata", nodedata);
    // console.log("nodedata.app_name: ", nodedata.app_name);
    if (nodedata.app_name !== undefined) {

      const allNodes = cy.nodes().jsons();
      // console.log("allNodes: ", allNodes)
      for (var nodekey in allNodes) {
        const currentNode = allNodes[nodekey];
        // console.log("Current node: ", currentNode);
        if (currentNode.data.isButton && currentNode.data.attachedTo !== nodedata.id) {

          if (currentNode.data.buttonType === "condition-drag") {
            continue
          }

          cy.getElementById(currentNode.data.id).remove();
        }

      }
    }


    // Skipping node editing if it's the selected one
    if (cy !== undefined && cy !== null) {
      const typeIds = cy.elements('node:selected').jsons();
      for (var idkey in typeIds) {
        const item = typeIds[idkey]
        if (item.data.id === nodedata.id) {
          return
        }
      }
    }
    //if (nodedata.id === selectedAction.id || nodedata.id === selectedTrigger.id) {
    //	return
    //}
	  //
    if (nodedata.name === "switch" || nodedata.app_id === "shuffle_agent") {
		return
	}

    var parsedStyle = {
      "border-width": "2px",
      "font-size": "18px",
      //"cursor": "default",
    }

    if ((nodedata.app_name === "Testing" || nodedata.app_name === "Shuffle Tools") && !nodedata.isStartNode) {
      parsedStyle = {
        "border-width": "2px",
        "font-size": "0px",
      };
    }

    event.target.stop().animate(
      {
        style: parsedStyle,
      },
      {
        duration: animationDuration,
      }
    );

    const outgoingEdges = event.target.outgoers("edge");
    const incomingEdges = event.target.incomers("edge");
    if (outgoingEdges.length > 0) {
      outgoingEdges.removeClass("hover-highlight");
    }

    if (incomingEdges.length > 0) {
      outgoingEdges.removeClass("hover-highlight");
    }
  };

  const buttonColor = "rgba(255,255,255,0.9)";
  const buttonBackgroundColor = "#1f2023";
  const addStartnodeButton = (event) => {
    var parentNode = cy.$("#" + event.target.data("id"));
    if (parentNode.data("isButton") || parentNode.data("buttonId")) return;

    if (parentNode.data("isStartNode")) {
      return;
    }

    var xDiff = 0
    var yDiff = 0
    if (parentNode.data("app_id") === "shuffle_agent") {
      xDiff = 70 
    }

    const px = parentNode.position("x") - 65 - xDiff;
    const py = parentNode.position("y") - 45 - yDiff;
    const circleId = (newNodeId = uuidv4());

    parentNode.data("circleId", circleId);

    const iconInfo = {
      icon: "M9.4 2H15V12H8L7.6 10H2V17H0V0H9L9.4 2ZM9 10H11V8H13V6H11V4H9V6L8 4V2H6V4H4V2H2V4H4V6H2V8H4V6H6V8H8V6L9 8V10ZM6 6V4H8V6H6ZM9 6H11V8H9V6Z",
      iconColor: buttonColor,
      iconBackgroundColor: buttonBackgroundColor,
    };

    const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
    const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);

    cy.add({
      group: "nodes",
      data: {
        weight: 30,
        id: circleId,
        name: "TEEEXT",
        isButton: true,
        buttonType: "set_startnode",
        attachedTo: event.target.data("id"),
        icon: svgpin_Url,
        iconBackground: iconInfo.iconBackgroundColor,
        is_valid: true,
      },
      position: { x: px, y: py },
      locked: true,
    });
  };

  const addRunCountButton = (event) => {
    // Count executions?
    // Maybe it shouldn't be onclick?
  }

  const addConditionDraggers = (event, allElements, branches) => {
    const nodedata = event.target.data()
    const position = event.target.position()

    var conditions = []
    const foundParam = nodedata.parameters.find((param) => param.name.toLowerCase() === "conditions")

    try {
      conditions = JSON.parse(foundParam.value)
    } catch (e) {
      //toast("Failed parsing conditions: ", e)
    }

    // Test conditions 
    if (conditions === undefined || conditions === null || typeof conditions !== "object") {
      return
    }

    // Look for if it has the "Else" condition or not
    const elseindex = conditions.findIndex((condition) => condition.name.toLowerCase() === "else")
    const parentId = nodedata.id

    // Force following of Else at the least
    const newId = uuidv5(parentId, uuidv5.URL)
    if (elseindex === -1) {
      conditions.push({
        name: "Else",
        check: "Else",
        id: newId,
        parent_source: parentId,
      })
    } else {
      conditions[elseindex].id = newId
    }

    // 4 conditions (with else) = 300px -> 75px each
    const parentHeight = (conditions.length * 75) * 0.75


    var startheight = -parentHeight / 2
    var newnodes = []
    for (let conditionkey in conditions) {
      var circleId = conditions[conditionkey].id === undefined ? (newNodeId = uuidv4()) : conditions[conditionkey].id

      // Check if circleId is a valid uuid or not
      if (circleId === undefined || circleId === null) {
        circleId = uuidv4()
      }

      if (!isUUID(circleId)) {
        if (conditions[circleId].name !== undefined && conditions[circleId].name !== null) {
          circleId = uuidv5(conditions[circleId].name, uuidv5.URL)
        } else {
          circleId = uuidv4()
          conditions[conditionkey].name = circleId
          conditions[conditionkey].id = circleId
        }
      }

      // Check if circleId already exists as a node
      if (cy !== undefined && cy !== null) {
        const existingNode = cy.getElementById(circleId)
        if (existingNode !== undefined && existingNode !== null && existingNode.length > 0) {
          continue
        }
      }

      // 1. Create "small" nodes at each point along the section based on the amount of conditions
      // 2. Make these conditions have edgehandles
      // 3. Make these conditions have a "drag" handle
      const px = position.x + 65
      const py = position.y + startheight

      console.log("Y height: ", startheight)

      const node = {
        group: "nodes",
        data: {
          name: conditions[conditionkey].name,
          id: circleId,
          buttonType: "condition-drag",
          attachedTo: nodedata.id,
          is_valid: true,
        },
        position: {
          x: px,
          y: py,
        },
        locked: true,
      }

      newnodes.push(node)

      // Check if ANY of the incoming branches has the id as source
      if (branches !== undefined && branches !== null && branches.length > 0) {
        for (let branchkey in branches) {
          const branch = branches[branchkey]
          if (branch.source_id !== circleId) {
            continue
          }

          const branchid = uuidv4()
          newnodes.push({
            group: "edges",
            data: {
              id: branchid,
              _id: branchid,

              source: circleId,
              target: branch.destination_id,
              label: branch.label,
              conditions: branch.conditions,
              hasErrors: branch.has_errors,
              decorator: false,
              parent_source: parentId,
            }
          })
        }
      }

      startheight = startheight + parentHeight / (conditions.length - 1)
    }

    if (cy !== undefined && cy !== null) {
      cy.add(newnodes)
    } else {
      var newelements = elements
      if (allElements !== undefined) {
        newelements = allElements
      }

      for (let nodekey in newnodes) {
        newelements.push(newnodes[nodekey])
      }

      console.log("ELEMENTS: ", newelements)
      setElements(newelements)
    }
  }

  const addCopyButton = (event) => {
    var parentNode = cy.$("#" + event.target.data("id"));
    if (parentNode.data("isButton") || parentNode.data("buttonId")) return;

	var xDiff = 0
	var yDiff = 0
	if (parentNode.data("app_id") === "shuffle_agent") {
		xDiff = 70 
	}

    const px = parentNode.position("x") - 65 - xDiff
    const py = parentNode.position("y") - 5 - yDiff
    const circleId = (newNodeId = uuidv4());

    parentNode.data("circleId", circleId);

    const iconInfo = {
      icon: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z",
      iconColor: buttonColor,
      iconBackgroundColor: buttonBackgroundColor,
    };

    const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
    const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);

    cy.add({
      group: "nodes",
      data: {
        weight: 30,
        id: circleId,
        name: "TEEEXT",
        isButton: true,
        buttonType: "copy",
        attachedTo: event.target.data("id"),
        icon: svgpin_Url,
        iconBackground: iconInfo.iconBackgroundColor,
        is_valid: true,
      },
      position: { x: px, y: py },
      locked: true,
    });
  };

  const addActionSuggestions = (nodedata, event) => {
    if (nodedata.type !== "ACTION") {
      return
    }

    var parentNode = cy.$("#" + event.target.data("id"))
    if (parentNode.data("isButton") || parentNode.data("buttonId")) {
      return
    }

    const px = parentNode.position("x") + 0;
    const py = parentNode.position("y") + 100;

    const parentlabel = parentNode.data("label")?.toLowerCase().replace(" ", "_")
    const parentname = parentNode.data("app_name")?.toLowerCase().replace(" ", "_")
    if (!parentlabel?.startsWith(parentname) + "_") {
      return
    }

    // Check if action has changed
    const parentAppId = parentNode.data("app_id")
    const parentActionname = parentNode.data("name")
    for (var appkey in apps) {
      const curapp = apps[appkey]

      if (curapp.id !== parentAppId) {
        continue
      }

      if (curapp.actions === undefined || curapp.actions === null || curapp.actions.length === 0) {
        continue
      }

      var startIndex = curapp.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0)
      if (startIndex === -1) {
        startIndex = 0
      }

      if (curapp.actions[startIndex].name !== parentActionname) {
        console.log("Return 2")
        return
      }

      break
    }

    console.log("CONTINUE EVEN WHEN FIELDS ARE FILLED")

    const iconInfo = {
      icon: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z",
      iconColor: buttonColor,
      iconBackgroundColor: buttonBackgroundColor,
    };

    const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
    const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);

    // 1. Find the app
    // 2. Loop the apps' actions
    // 3. Find actions based on category label IF it exists

    var addedLabels = []
    for (let appKey in apps) {
      const curapp = apps[appKey]
      if (curapp.name.toLowerCase().replace(" ", "_") !== parentname) {
        continue
      }

      if (curapp.actions === undefined || curapp.actions === null || curapp.actions.length === 0) {
        continue
      }

      for (let actionKey in curapp.actions) {
        const curaction = curapp.actions[actionKey]

        // Check if this is the current action already
        if (parentNode.data("name") == curaction.name) {
          continue
        }

        if (curaction.category_label !== undefined && curaction.category_label !== null && curaction.category_label.length > 0) {
          if (addedLabels.includes(curaction.category_label[0])) {
            continue
          }

          if (curaction.category_label[0].replaceAll("_", " ").toLowerCase() === "no label") {
            continue
          }

          cy.add({
            group: "nodes",
            data: {
              weight: 30,
              id: uuidv4(),
              label: curaction.category_label[0],
              attachedTo: event.target.data("id"),
              is_valid: true,
              buttonType: "ACTIONSUGGESTION",
            },
            position: {
              x: px,
              y: py + (addedLabels.length * 50),
            },
            locked: true,
          })

          addedLabels.push(curaction.category_label[0])
          if (addedLabels.length >= 2) {
            break
          }
        }
      }

      break
    }
  }

  const addSuggestionButtons = (nodedata, event) => {
    //console.log("Skipping Adding suggestion buttons")
    //return
    // Skipping add for now. Should Re-enable

    // Add a button for autocompletion based on input
    if (nodedata.type === "ACTION") {
      /*
      const color = "#34a853" 
  
      // Fix icon
      const iconInfo = {
        icon: "M7.5 5.6 10 7 8.6 4.5 10 2 7.5 3.4 5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17 19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22 7l-1.4-2.5zm-7.63 5.29a.9959.9959 0 0 0-1.41 0L1.29 18.96c-.39.39-.39 1.02 0 1.41l2.34 2.34c.39.39 1.02.39 1.41 0L16.7 11.05c.39-.39.39-1.02 0-1.41l-2.33-2.35zm-1.03 5.49-2.12-2.12 2.44-2.44 2.12 2.12-2.44 2.44z",
        iconColor: buttonColor,
        iconBackgroundColor: buttonBackgroundColor,
      };
  
      const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
      const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
  
      const decoratorNode = {
        position: {
          x: event.target.position().x + 0,
          y: event.target.position().y + 65,
        },
        locked: true,
        data: {
          isButton: true,
          isValid: true,
          is_valid: true,
          //label: "+",
          attachedTo: nodedata.id,
          imageColor: color,
          buttonType: "suggestion", 
          icon: svgpin_Url,
          iconBackground: iconInfo.iconBackgroundColor,
        },
      };
  
      cy.add(decoratorNode);
      */
    }

    if (workflowRecommendations === undefined || workflowRecommendations === null || workflowRecommendations.length === 0) {
      return
    }

    var parentNode = cy.$("#" + event.target.data("id"));
    if (parentNode.data("isButton") || parentNode.data("buttonId")) return;

    const px = parentNode.position("x") + 0;
    const py = parentNode.position("y") + 200;
    const circleId = (newNodeId = uuidv4());

    parentNode.data("circleId", circleId);

    var startHeight = 0
    for (let recKey in workflowRecommendations) {
      const rec = workflowRecommendations[recKey]
      if (rec.action_id !== nodedata.id) {
        continue
      }

      if (rec.recommendations === undefined || rec.recommendations === null || rec.recommendations.length === 0) {
        continue
      }

      for (let recIndex in rec.recommendations) {
        const parsedRec = rec.recommendations[recIndex]
        console.log("REC: ", parsedRec)

        const foundVersion = parsedRec.app_version !== undefined && parsedRec.app_version !== null && parsedRec.app_version !== "" ? parsedRec.app_version : "1.1.0"
        const foundApp = apps.find((app) => app.app_name === parsedRec.app_name && app.app_version === foundVersion)
        // Find out if foundApp is shuffle tools, and if so, add the correct image based on name

        const largeImage = parsedRec.large_image !== undefined && parsedRec.large_image !== null && parsedRec.large_image !== "" ? parsedRec.large_image : foundApp === undefined || foundApp === null ? theme.palette.defaultImage : foundApp.large_image

        const uuid = uuidv4()
        const attachedToId = event.target.data("id")
        // Check if parsedRec.app_action exists already as a node under this one
        const branches = cy.edges().jsons()
        var found = false
        for (let branchKey in branches) {
          const branch = branches[branchKey]
          if (branch.data.source !== attachedToId) {
            continue
          }

          const targetNode = cy.getElementById(branch.data.target)
          if (targetNode === undefined || targetNode === null) {
            continue
          }

          if (targetNode.data("name") === parsedRec.app_action) {
            console.log("Found existing node (action name): ", targetNode)
            found = true
          }

          // FIXME: This could potentially be removed
          if (targetNode.data("app_id") === parsedRec.app_id) {
            console.log("Found existing node (id): ", targetNode)
            found = true
          }
        }

        // Skip the suggestion if it already exists
        if (found) {
          continue
        }

        // Checks for src/dst (e.g. trigger = src usually)
        const isTarget = true

        var name = parsedRec.app_action
        if (parsedRec.app_action === "subflow") {
          name = "Shuffle Workflow"
        } else if (parsedRec.app_action === "user_input") {
          name = "User Input"
        }

        const newaction = {
          name: name,
          label: parsedRec.app_action,
          label_replaced: parsedRec.app_action.replace("_", " ", -1),

          id: uuid,
          app_name: parsedRec.app_name,
          app_version: foundVersion,
          app_id: parsedRec.app_id,
          sharing: false,
          private_id: "",
          isStartNode: false,
          large_image: largeImage,
          is_valid: true,
          isSuggestion: true,
          isTarget: isTarget,
          attachedTo: attachedToId,

          finished: false,
        }

        cy.add({
          group: "nodes",
          data: newaction,
          position: {
            x: px + startHeight,
            y: py,
          },
          locked: true,
        });

        cy.add({
          group: "edges",
          data: {
            source: event.target.data("id"),
            target: uuid,
            decorator: true,
          }
        })

        startHeight += 100
      }

      console.log("Got Rec: ", rec)
      break
    }
  }

  const addDeleteButton2 = (event) => {
    var parentNode = cy.$("#" + event.target.data("id"));
    if (parentNode.data("isButton") || parentNode.data("buttonId")) return;

	var xDiff = 0
	var yDiff = 0
	if (parentNode.data("app_id") === "shuffle_agent") {
		xDiff = 70 
	}

    const px = parentNode.position("x") + 100 - xDiff;
    const py = parentNode.position("y") + 35 - yDiff;
    const circleId = (newNodeId = uuidv4());

    parentNode.data("circleId", circleId);

    const iconInfo = {
      icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
      iconColor: buttonColor,
      iconBackgroundColor: buttonBackgroundColor,
    };
    const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
    const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);

    cy.add({
      group: "nodes",
      data: {
        weight: 30,
        id: circleId,
        name: "This is autocomplete",
        buttonType: "delete",
        attachedTo: event.target.data("id"),
        icon: svgpin_Url,
        iconBackground: iconInfo.iconBackgroundColor,
        is_valid: true,
      },
      position: { x: px, y: py },
      locked: true,
    });
  };

  const addDeleteButton = (event) => {
    var parentNode = cy.$("#" + event.target.data("id"));
    if (parentNode.data("isButton") || parentNode.data("buttonId")) return;

	var xDiff = 0
	var yDiff = 0
	if (parentNode.data("app_id") === "shuffle_agent") {
		xDiff = 70 
	}

    const px = parentNode.position("x") - 65 - xDiff;
    const py = parentNode.position("y") + 35 - yDiff;
    const circleId = (newNodeId = uuidv4());

    parentNode.data("circleId", circleId);

    const iconInfo = {
      icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
      iconColor: buttonColor,
      iconBackgroundColor: buttonBackgroundColor,
    };
    const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
    const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);

    cy.add({
      group: "nodes",
      data: {
        weight: 30,
        id: circleId,
        name: "TEEEXT",
        isButton: true,
        buttonType: "delete",
        attachedTo: event.target.data("id"),
        icon: svgpin_Url,
        iconBackground: iconInfo.iconBackgroundColor,
        is_valid: true,
      },
      position: { x: px, y: py },
      locked: true,
    });
  };

  const onNodeHover = (event) => {
    const nodedata = JSON.parse(JSON.stringify(event.target.data()))

    const cytoscapeElement = document.getElementById("cytoscape_view")
    if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
      cytoscapeElement.style.cursor = "pointer"
    }

    sendStreamRequest({
      "item": "node",
      "type": "hover",
      "id": nodedata.id,
    })

    if (nodedata.finished === false) {
      // Should just be 1, so this should be fast enough :3
      const incomingEdges = event.target.incomers("edge").jsons()
      if (incomingEdges !== undefined && incomingEdges !== null) {
        for (var i = 0; i < incomingEdges.length; i++) {
          // Find the actual edge
          const edge = cy.getElementById(incomingEdges[i].data.id)
          if (edge === undefined || edge === null) {
            console.log("edge is null or undefined")
            continue
          }

          // Set the edge to be dashed
          edge.style("target-arrow-color", theme.palette.text.primary)
          edge.style("line-style", "solid")
          edge.style("line-gradient-stop-colors", [theme.palette.text.primary, theme.palette.text.primary])
        }
      }

      return
    }



    //var parentNode = cy.$("#" + event.target.data("id"));
    //if (parentNode.data("isButton") || parentNode.data("buttonId")) return;

    if (nodedata.app_name !== undefined && !workflow.public === true) {
      const allNodes = cy.nodes().jsons();

      if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule") {
        var found = false;
        for (let nodekey in allNodes) {
          const currentNode = allNodes[nodekey];
          if (
            currentNode.data.attachedTo === nodedata.id &&
            currentNode.data.isDescriptor
          ) {
            found = true;
            break;
          }
        }

        if (!found) {
          // Find how many executions it has 
          var executions = 0
          const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
          const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
          const decoratorNode = {
            position: {
              x: event.target.position().x + 44,
              y: event.target.position().y + 44,
            },
            locked: true,
            data: {
              isDescriptor: true,
              isValid: true,
              is_valid: true,
              isTrigger: true,
              label: `${matchingExecutions.length}`,
              attachedTo: nodedata.id,
              imageColor: color,
              hasExecutions: true,
            },
          };

          cy.add(decoratorNode)
        }
      }

      var found = false;
      for (var _key in allNodes) {
        const currentNode = allNodes[_key]
        // console.log("CURRENT NODE: ", currentNode)

        if ((currentNode.data.buttonType === "ACTIONSUGGESTION" || currentNode.data.isButton || currentNode.data.isSuggestion) && currentNode.data.attachedTo !== nodedata.id) {

          if (currentNode.data.buttonType === "condition-drag") {
            continue
          }

          cy.getElementById(currentNode.data.id).remove()
        }

        /*if (
          currentNode.data.isSuggestion &&
          currentNode.data.attachedTo !== nodedata.id
        ) {
          cy.getElementById(currentNode.data.id).remove();
        }*/

        if (currentNode.data.isButton && currentNode.data.attachedTo === nodedata.id) {
          found = true;
        }
      }

      if (nodedata.name === "switch") {
        addConditionDraggers(event)
        return
      }

      if (!found) {
        addDeleteButton(event)

        if (nodedata.type === "TRIGGER") {
          if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") {
            addCopyButton(event);
        } else {
            // Check how many executions from the source
            addRunCountButton(event);
          }
        } else {

          addCopyButton(event);

		  // if (nodedata.app_id !== "shuffle_agent") {
          addStartnodeButton(event);
      // }
        }

        // autocomplete
        // right click
        // suggestions
        addActionSuggestions(nodedata, event);

        if (workflow.actions.length < 4) {
          addSuggestionButtons(nodedata, event);
        } else {
          //console.log("Too many actions to suggest (for now)")
        }
      }
    }

    if (nodedata.name === "switch") {
      return
    }

    var parsedStyle = {
      "border-width": "6px",
      "border-opacity": ".7",
      "font-size": nodedata.app_id !== "shuffle_agent" && "25px",
      //"cursor": "pointer",
    }

    if (nodedata.buttonType === "ACTIONSUGGESTION") {
      parsedStyle["font-size"] = "18px"
    }

    const typeIds = cy.elements('node:selected').jsons();
    for (var idkey in typeIds) {
      const item = typeIds[idkey]
      if (item.data.id === nodedata.id) {
        //console.log("items: ", item.data.id, nodedata.id)
        parsedStyle["border-width"] = "6px"
        break
      }
    }

    if (nodedata.type !== "COMMENT") {
      parsedStyle.color = theme.palette.text.primary;
    }

    if (event.target !== undefined && event.target !== null) {
      event.target.stop().animate(
        {
          style: parsedStyle,
        },
        {
          duration: animationDuration,
        }
      );
    }

    const outgoingEdges = event.target.outgoers("edge");
    const incomingEdges = event.target.incomers("edge");
    if (outgoingEdges.length > 0) {
      outgoingEdges.addClass("hover-highlight");
    }

    if (incomingEdges.length > 0) {
      outgoingEdges.addClass("hover-highlight");
    }
  }

  const onEdgeHoverOut = (event) => {
    if (event === null || event === undefined || event.target === null || event.target === undefined) {
      event.target.removeStyle();
      return;
    }

    const edgeData = event.target.data();
    if (edgeData.decorator === true) {

      // Defaults
      event.target.style("target-arrow-color", "#555555")
      event.target.style("line-style", "dashed")
      event.target.style("line-gradient-stop-colors", ["#555555", "#555555"])

      return;
    }

    const cytoscapeElement = document.getElementById("cytoscape_view")
    if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
      cytoscapeElement.style.cursor = "default"
    }

    //event.target.removeStyle();
  };

  // This is here to have a proper transition for lines
  const onEdgeHover = (event) => {
    if (event === null || event === undefined || event.target === null || event.target === undefined) {
      return;
    }

    const edgeData = event.target.data();
    if (edgeData.decorator === true) {
      // Set color of it to white and not stripled
      event.target.style("target-arrow-color", theme.palette.text.primary)
      event.target.style("line-style", "solid")
      event.target.style("line-gradient-stop-colors", [theme.palette.text.primary, theme.palette.text.primary])

      return;
    }

    // FIXME: Color problem. Do later 
    //sendStreamRequest({
    //  "item": "edge",
    //  "type": "hover",
    //  "id": edgeData.id,
    //})

    const cytoscapeElement = document.getElementById("cytoscape_view")
    if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
      cytoscapeElement.style.cursor = "pointer"
    }

    //const sourcecolor = cy
    //  .getElementById(event.target.data("source"))
    //  .style("border-color");
    //const targetcolor = cy
    //  .getElementById(event.target.data("target"))
    //  .style("border-color");

    ////console.log(sourcecolor, targetcolor)
    //if (
    //  sourcecolor !== null &&
    //  sourcecolor !== undefined &&
    //  targetcolor !== null &&
    //  targetcolor !== undefined && 
    //	!sourcecolor.includes("rgb") &&
    //	!targetcolor.includes("rgb") 
    //) {
    //	console.log(sourcecolor)
    //	console.log(targetcolor)

    //	if (event.target !== null && event.target.value !== null) {
    //		event.target.animate({
    //			style: {
    //				"target-arrow-color": targetcolor,
    //				"line-fill": "linear-gradient",
    //				"line-gradient-stop-colors": [sourcecolor, targetcolor],
    //				"line-gradient-stop-positions": [0, 1],
    //			},
    //			duration: animationDuration,
    //		})
    //	} else {
    //		event.target.animate({
    //			style: {
    //				"target-arrow-color": targetcolor,
    //				"line-fill": "linear-gradient",
    //  			"line-gradient-stop-colors": ["#41dcab", "#41dcab"],
    //				"line-gradient-stop-positions": [0, 1],
    //			},
    //			duration: animationDuration,
    //		})

    //	}
    //}

    if (event.target !== undefined && event.target !== null) {

      // If decorator and hovered
      // Set color to white





      //const targetcolor = "#66a8b1"
      //const parsedStyle = {
      //	"width": "10px",
      //	"font-size": "18px",
      //	"target-arrow-color": "#66a8b1",
      //	"color": "#66a8b1",
      //}

      //event.target.addClass("shuffle-hover-highlight");

      //console.log("Style1: ", event.target)
      //console.log("Style: ", event.target.style())

      //event.target.animate(
      //	{
      //		style: parsedStyle,
      //	},
      //	{
      //		duration: animationDuration,
      //	}
      //)
    }
  }

  // Thanks to:
  // Calculates how a branch should curve (it's still weird~)
  // https://codepen.io/guillaumethomas/pen/xxbbBKO
  const calculateEdgeCurve = (sourcenodePosition, destinationnodePosition) => {
    const xParsed = destinationnodePosition.x - sourcenodePosition.x
    const yParsed = destinationnodePosition.y - sourcenodePosition.y

    const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed)
    const costheta = xParsed / z
    const alpha = 0.3
    var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta]
    var controlPointWeight = [alpha, 1 - alpha]

    //'control-point-weight': ['0.33', '0.66'],
    //var controlPointWeight = ["0.33", "0.66"]
    //var controlPointDistance = ["33%", "-66%"]
    //var controlPointWeight = ["0.00", "1.00"]
    /*
    if (yParsed !== 0) {
      //const degreeFound = Math.atan2(xParsed / yParsed)
      const degreeFound = Math.atan2(xParsed, yParsed) * 180 / Math.PI

      if (degreeFound > 90 && degreeFound < 180) {
        console.log("TOPRIGHT")
      } else if (degreeFound < 90 && degreeFound > 0) {
        console.log("BOTTOMRIGHT")
      } else if (degreeFound < 0 && degreeFound > -90) {
        console.log("BOTTOMLEFT")
        //controlPointWeight = ["0.20", "0.80"]
        //controlPointWeight = "0.7"
        //controlPointDistance = "50%" 

      } else if (degreeFound < -90 && degreeFound > -180) {
        console.log("TOPLEFT")
      } else {
        console.log("STRAIGHT!")
      }
    }
    */
    if (isNaN(controlPointDistance[0])) {
      controlPointDistance[0] = 0
    }
    if (isNaN(controlPointDistance[1])) {
      controlPointDistance[1] = 0
    }

    if (isNaN(controlPointWeight[0])) {
      controlPointWeight[0] = 0
    }
    if (isNaN(controlPointWeight[1])) {
      controlPointWeight[1] = 0
    }

    return {
      "distance": controlPointDistance,
      "weight": controlPointWeight,
    }
  }

  function roundBase64Image(base64, radius = 12) {
    if (!base64 || typeof base64 !== "string") {
      return Promise.resolve(base64);
    }

    return new Promise((resolve) => {
      const img = new Image();
      if (!base64.startsWith("data:")) {
        img.crossOrigin = "anonymous";
      }

      img.onload = () => {
        const size = Math.min(img.width, img.height);
        if (size === 0) {
          resolve(base64);
          return;
        }

        const canvas = document.createElement("canvas");
        canvas.width = size;
        canvas.height = size;
  
        const ctx = canvas.getContext("2d");
  
        // Draw rounded rect mask
        ctx.beginPath();
        ctx.moveTo(radius, 0);
        ctx.lineTo(size - radius, 0);
        ctx.quadraticCurveTo(size, 0, size, radius);
        ctx.lineTo(size, size - radius);
        ctx.quadraticCurveTo(size, size, size - radius, size);
        ctx.lineTo(radius, size);
        ctx.quadraticCurveTo(0, size, 0, size - radius);
        ctx.lineTo(0, radius);
        ctx.quadraticCurveTo(0, 0, radius, 0);
        ctx.closePath();
        ctx.clip();
  
        // Draw image centered & cropped square
        const sx = (img.width - size) / 2;
        const sy = (img.height - size) / 2;
        ctx.drawImage(img, sx, sy, size, size, 0, 0, size, size);
  
        try {
          resolve(canvas.toDataURL("image/png"));
        } catch (e) {
          resolve(base64);
        }
      };

      img.onerror = () => {
        resolve(base64);
      };
  
      img.src = base64;
    });
  }
  
  const setupGraph = async (inputworkflow) => {
    // Reset cytoscape nodes and branches
    if (cy !== undefined && cy !== null) {
      if (inputworkflow.actions !== undefined && inputworkflow.actions !== null && inputworkflow.actions.length > 0) {
        //cy.remove('*')
      }
    }

    if (inputworkflow.actions === undefined || inputworkflow.actions === null) {
      inputworkflow.actions = []
    }

    if (inputworkflow.branches === undefined || inputworkflow.branches === null) {
      inputworkflow.branches = []
    }

    if (inputworkflow.triggers === undefined || inputworkflow.triggers === null) {
      inputworkflow.triggers = []
    }

    if (inputworkflow.comments === undefined || inputworkflow.comments === null) {
      inputworkflow.comments = []
    }

    if (inputworkflow.visual_branches === undefined || inputworkflow.visual_branches === null) {
      inputworkflow.visual_branches = []
    }

    const actions = await Promise.all(inputworkflow.actions.map(async (action) => {
      const node = {};

      if (!action.isStartNode && action.app_name === "Shuffle Tools") {
        const iconInfo = GetIconInfo(action)
        const iconViewBox = iconInfo.svgViewBox || `0 0 ${svgSize} ${svgSize}`;
        const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="${iconViewBox}" version="1.1" xmlns="http://www.w3.org/2000/svg">${buildIconSvgPath(iconInfo, "white")}</svg>`;
        const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
        action.large_image = svgpin_Url;
        action.fillGradient = iconInfo.fillGradient;
        action.fillstyle = "solid";
        if (
          action.fillGradient !== undefined &&
          action.fillGradient !== null &&
          action.fillGradient.length > 0
        ) {
          action.fillstyle = "linear-gradient";
        } else {
          action.iconBackground = iconInfo.iconBackgroundColor;
        }
      } else if (action.app_name === "Integration Framework" || action.app_name === "Singul") {
        const iconInfo = GetIconInfo(action)
        if (iconInfo !== undefined && iconInfo !== null) {
          action.fillGradient = iconInfo.fillGradient
          action.iconBackground = iconInfo.iconBackgroundColor
          action.fillstyle = "linear-gradient"
        }
      }else if(!action.isStartNode) {
        // This is to round the corners of the image
        const originalBase64 = action.large_image !== undefined && action.large_image !== null && action.large_image !== "" ? action.large_image : theme.palette.defaultImage
        const roundedImage = await roundBase64Image(originalBase64, 16);
        action = {...action, large_image: roundedImage}

      }

      node.position = action.position;
      node.data = action;

      node.data.id = action["id"];
      node.data._id = action["id"];
      node.data.type = "ACTION";
      node.isStartNode = action["id"] === inputworkflow.start;

      if (node.data.errors !== undefined && node.data.errors !== null && node.data.errors.length > 0) {
        node.data.is_valid = false
        node.is_valid = false
      }

      if (inputworkflow.public === true) {
        node.data.is_valid = true
        node.is_valid = true
      }


      var example = "";
      if (
        action.example !== undefined &&
        action.example !== null &&
        action.example.length > 0
      ) {
        example = action.example;
      }

      node.data.example = example;

      if (inputworkflow?.id !== undefined && originalWorkflow?.id !== undefined && inputworkflow?.id !== originalWorkflow?.id && originalWorkflow.actions !== undefined && originalWorkflow.actions !== null && originalWorkflow.actions.length > 0) {
        // Find the node in the original workflow
        var inParent = false
        for (var i = 0; i < originalWorkflow.actions.length; i++) {
          const originalAction = originalWorkflow.actions[i]
          if (originalAction.id === action.id) {
            inParent = true
            break
          }
        }

        if (inParent === true) {
          setTimeout(() => {
            const foundnode = cy.getElementById(action.id)
            if (foundnode !== undefined && foundnode !== null) {
              const parsedStyle = {
                "border-width": "2px",
                "border-opacity": "1",
                "border-color": "#40E0D0",
                "opacity": "0.4",
              }

              const animationDuration = 150
              foundnode.stop().animate(
                {
                  style: parsedStyle,
                },
                {
                  duration: animationDuration,
                }
              )
            }
          }, 500)
        }
      }

      return node
    }))

    // What are these again? Where are they used?
    const decoratorNodes = []

    /*
    // Removed for now as it wasn't really that helpful 
      const decoratorNodes = inputworkflow.actions.map((action) => {
        if (!action.isStartNode) {
          if (action.app_name === "Testing") {
            return null
          } else if (action.app_name === "Shuffle Tools") {
            return null
          } else if (action.app_name === "Integration Framework" || action.app_name === "Singul") {
            return null
          }
        }
  
      if (action.id === undefined || action.id === null) {
        return null
      }
  
      if (action.position === undefined || action.position === null || action.position.x === undefined || action.position.x === null || action.position.y === undefined || action.position.y === null) {
        return null
      }
  
        const iconInfo = GetIconInfo(action);
        const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
        const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
  
        const offset = action.isStartNode ? 36 : 44;
  
        const decoratorNode = {
          position: {
            x: action.position.x + offset,
            y: action.position.y + offset,
          },
          locked: true,
          data: {
            isDescriptor: true,
            isValid: true,
            is_valid: true,
            label: "",
            image: svgpin_Url,
            imageColor: iconInfo.iconBackgroundColor,
            attachedTo: action.id,
          },
        }
        return decoratorNode
      })
    */


    const foundtriggers = inputworkflow.triggers.map((trigger) => {
      const node = {};
      node.position = trigger.position;

      // Search triggers array for it where the name is matching and set image
      if (trigger.large_image === undefined || trigger.large_image === null || trigger.large_image.length === 0) {
        var foundTrigger = triggers.find((t) => t.name === trigger.name)
        if (foundTrigger !== undefined && foundTrigger !== null) {
          console.log("Autofilled missing trigger image")
          trigger.large_image = foundTrigger.large_image
        }
      }

      node.data = trigger;
      node.data._id = trigger["id"];
      node.data.id = trigger["id"];
      node.data.type = "TRIGGER";

      // Adds the correct branching for same-workflow trigger
      if (trigger?.trigger_type === "SUBFLOW" && trigger?.parameters !== undefined && trigger?.parameters !== null && trigger?.parameters.length > 0) {
        var foundTargetNode = ""
        var sameWorkflow = false
        for (var key in trigger.parameters) {
          if (trigger.parameters[key].name === "workflow" && trigger.parameters[key].value === inputworkflow.id) {
            sameWorkflow = true
          }

          if (trigger.parameters[key].name === "startnode" && trigger.parameters[key].value !== "") {
            foundTargetNode = trigger.parameters[key].value
          }
        }

        if (sameWorkflow && foundTargetNode !== "") {
          const newid = uuidv4()
          const newbranch = {
            id: newid,
            _id: newid,
            source: trigger.id,
            source_id: trigger.id,
            target: foundTargetNode,
            destination_id: foundTargetNode,

            conditions: [],
            has_errors: false,
            decorator: true,
            label: "Subflow",
          }

          if (inputworkflow.visual_branches !== undefined) {
            if (inputworkflow.visual_branches === null) {
              inputworkflow.visual_branches = [newbranch]
            } else if (inputworkflow.visual_branches.length === 0) {
              inputworkflow.visual_branches.push(newbranch)
            } else {
              const foundIndex = inputworkflow.visual_branches.findIndex(
                (branch) => branch.source_id === newbranch.source_id
              )

              if (foundIndex !== -1) {
                //console.log("Already found subflow branch")
              } else {
                inputworkflow.visual_branches.push(newbranch);
              }
            }
          } else {
            inputworkflow.visual_branches = [newbranch]
          }

        }

      }

      if (inputworkflow?.id !== undefined && originalWorkflow?.id !== undefined && inputworkflow?.id !== originalWorkflow?.id && originalWorkflow.triggers !== undefined && originalWorkflow.triggers !== null && originalWorkflow.triggers.length > 0) {

        // Find the node in the original workflow
        var inParent = false
        for (var i = 0; i < originalWorkflow.triggers.length; i++) {
          const originalAction = originalWorkflow.triggers[i]
          if (originalAction.id === trigger.id) {
            inParent = true
            break
          }
        }

		if (inParent === false) {
			if (trigger.parent_controlled === true) {
				inParent = true
			}
		}

        if (inParent === true) {
          setTimeout(() => {
            const foundnode = cy.getElementById(trigger.id)
            if (foundnode !== undefined && foundnode !== null) {
              const parsedStyle = {
                "border-width": "2px",
                "border-opacity": "1",
                "border-color": "#40E0D0",
                "opacity": "0.4",
              }

              const animationDuration = 150
              foundnode.stop().animate(
                {
                  style: parsedStyle,
                },
                {
                  duration: animationDuration,
                }
              )
            }
          }, 500)
        }
      }

      return node;
    });

    var comments = [];
    if (
      inputworkflow.comments !== undefined &&
      inputworkflow.comments !== null &&
      inputworkflow.comments.length > 0
    ) {
      comments = inputworkflow.comments.map((comment) => {
        const node = {};
        node.position = comment.position;
        node.data = comment;

        node.data._id = comment["id"];
        node.data.type = "COMMENT";

        return node;
      });
    }

    var insertedNodes = [].concat(actions, foundtriggers, decoratorNodes, comments);
    insertedNodes = insertedNodes.filter((node) => node !== null);

    var edges = inputworkflow.branches.map((branch, index) => {
      const edge = {};
      var conditions = inputworkflow.branches[index].conditions;
      if (conditions === undefined || conditions === null) {
        conditions = [];
      }

      var label = "";
      if (conditions.length === 1) {
        label = conditions.length + " condition";
      } else if (conditions.length > 1) {
        label = conditions.length + " conditions";
      }

      // Verify if branch.source_id and branch.destination_id exists in triggers or actions
      /*
      var sourceExists = false;
      var destinationExists = false;
      for (var i = 0; i < insertedNodes.length; i++) {
        console.log("Insertednode: ", insertedNodes[i].data);
      if (insertedNodes[i].data._id === branch.source_id) {
        sourceExists = true;
      } 
      if (insertedNodes[i].data._id === branch.destination_id) {
        destinationExists = true;
      }
      }
  
      if (sourceExists === false || destinationExists === false) {
        console.log("Couldn't find source node for branch " + branch.id);
        return null;
      }
      */

      var parentcontrolled = false
      if (branch.parent_controlled !== undefined && branch.parent_controlled !== null && branch.parent_controlled === true) {
        parentcontrolled = true
      }

      edge.data = {
        id: branch.id,
        _id: branch.id,
        source: branch.source_id,
        target: branch.destination_id,
        label: label,
        conditions: conditions,
        hasErrors: branch.has_errors,
        decorator: false,
        parent_controlled: parentcontrolled,
      }

      // This is an attempt at prettier edges. The numbers are weird to work with.
      // Bezier curves
      //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html
      var sourcenode = actions.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id)
      var destinationnode = actions.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id)

      if (sourcenode === undefined) {
        sourcenode = foundtriggers.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id)
      }

      if (destinationnode === undefined) {
        destinationnode = foundtriggers.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id)
      }

      if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) {
        const edgeCurve = calculateEdgeCurve(sourcenode.position, destinationnode.position)
        edge.style = {
          'control-point-distance': edgeCurve.distance,
          'control-point-weight': edgeCurve.weight,
        }
      } else {
        console.log("FAILED node curve handling")
      }

      return edge;
    });

    if (inputworkflow.visual_branches !== undefined && inputworkflow.visual_branches !== null && inputworkflow.visual_branches.length > 0) {
      const visualedges = inputworkflow.visual_branches.map((branch, index) => {
        const edge = {};

        if (inputworkflow.branches[index] === undefined) {
          return {}
        }

        var conditions = inputworkflow.branches[index].conditions
        if (conditions === undefined || conditions === null) {
          conditions = [];
        }

        const label = "Subflow";
        edge.data = {
          id: branch.id,
          _id: branch.id,
          source: branch.source_id,
          target: branch.destination_id,
          label: label,
          decorator: true,
        }

        return edge
      });

      edges = edges.concat(visualedges)
    }


    // Verifies if a branch is valid and skips others
    var newedges = [];
    for (var edgeKey in edges) {
      var item = edges[edgeKey];
      if (item.data === undefined) {
        continue;
      }

      const sourcecheck = insertedNodes.find(
        (data) => data.data.id === item.data.source
      );
      const destcheck = insertedNodes.find(
        (data) => data.data.id === item.data.target
      );
      if (sourcecheck === undefined || destcheck === undefined) {
        continue;
      }

      newedges.push(item);
    }

    insertedNodes = insertedNodes.concat(newedges);
    setWorkflow(inputworkflow)

    // Reset view for cytoscape
    if (cy !== undefined && cy !== null) {
	  try {
      	cy.add(insertedNodes)
	  } catch (error) {
		  console.log("Error adding nodes to cytoscape (6): ", error)
	  }

	  try {
      	cy.fit(null, 250)
	  } catch (error) {
		console.log("Error fitting cytoscape (3): ", error)
	  }
    } else {
      setElements(insertedNodes)
    }

    const additionalNodes = inputworkflow.actions.map((action) => {
      // Looking for: el.data("name") != "switch" 
      if (action.name !== "switch") {
        return null
      }

      addConditionDraggers({
        target: {
          // Run data() function
          data: function () {
            return action
          },
          position: function () {
            return action.position
          }
        }
      },
        insertedNodes,
        inputworkflow.branches,
      )
    })
  }

  const removeNode = (nodeId) => {
    const selectedNode = cy.getElementById(nodeId);
    // console.log("selected node in removenode", selectedNode);
    if (selectedNode.data() === undefined) {
      console.log("No node to remove")
      return;
    }

    //console.log("Removing node: ", selectedNode.data("id"), "Action: ", selectedAction.id)

    // Get selected node

    if (selectedNode.data().type === "TRIGGER") {

      const triggerindex = workflow.triggers.findIndex(
        (data) => data.id === selectedNode.data().id
      );

      setSelectedTriggerIndex(triggerindex);
      if (selectedNode.data().trigger_type === "SCHEDULE") {
        setSelectedTrigger(selectedNode.data());
        stopSchedule(selectedNode.data(), triggerindex);
      } else if (selectedNode.data().trigger_type === "WEBHOOK") {
        setSelectedTrigger(selectedNode.data());
        deleteWebhook(selectedNode.data(), triggerindex);
      } else if (selectedNode.data().trigger_type === "EMAIL") {
        setSelectedTrigger(selectedNode.data());
        stopMailSub(selectedTrigger, triggerindex);
      } else if (selectedNode.data().trigger_type === "PIPELINE") {
        setSelectedTrigger(selectedNode.data());

        const pipelineConfig = {
          command: "",
          name: selectedNode.data().label,
          type: "delete",
          environment: selectedNode.data().environment,
          workflow_id: workflow.id,
          trigger_id: selectedNode.data().id,
          start_node: "",
        };

        submitPipeline(selectedNode.data(), triggerindex, pipelineConfig);
      }
    }

    const parsedSelection = cy.$(":selected");
    if (selectedNode.data().decorator === true && selectedNode.data("type") !== "COMMENT") {
      toast("This node can't be deleted.");
    } else {
      selectedNode.remove();

      setSelectedTrigger({})
      setSelectedEdge({})
      setSelectedAction({})
    }
  }


  if (isLoaded && setupSent === false) {
    setSetupSent(true)

    sendStreamRequest({
      "item": "workflow",
      "type": "enter",
      "id": workflow.id,
    })
  }

  const fetchRecommendations = (inputWorkflow) => {
    console.log("Disabled recommendations as they were too inaccurate")
    return

    const parsedWorkflow = JSON.parse(JSON.stringify(inputWorkflow))

    fetch(globalUrl + "/api/v1/workflows/recommend", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(parsedWorkflow),
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for usecases");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success !== false) {
          //console.log("recommendations: ", responseJson);

          if (responseJson.actions !== undefined && responseJson.actions !== null) {
            console.log("Got recommendations: ", responseJson.actions)

            //if (cy !== undefined && cy !== null) {
            //	cy.removeListener("mouseover", "node");
            //}

            setWorkflowRecommendations(responseJson.actions)

            //if (cy !== undefined && cy !== null) {
            //	cy.on("mouseover", "node", (e) => onNodeHover(e));
            //}
          } else {
            setWorkflowRecommendations([])
          }
        } else {
          setWorkflowRecommendations([])
        }
      })
      .catch((error) => {
        //toast("ERROR: " + error.toString());
        setWorkflowRecommendations([])
        console.log("ERROR getting usecases: " + error.toString());
      })
  }

  const fetchUsecases = () => {
    fetch(globalUrl + "/api/v1/workflows/usecases", {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for usecases");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success !== false) {
          setUsecases(responseJson)
        } else {
        }
      })
      .catch((error) => {
        //toast("ERROR: " + error.toString());
        console.log("ERROR getting usecases: " + error.toString());
      })
  }

  const getRevisionHistory = (workflow_id, revisionCount=50, turn=0, orgId="") => {
    let headers = {
      "Content-Type": "application/json",
      Accept: "application/json",
    }

    if (orgId !== "") {
      headers["Org-Id"] = orgId
    }

    fetch(`${globalUrl}/api/v1/workflows/${workflow_id}/revisions?count=${revisionCount}`, {
      method: "GET",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for workflows :O!");
        }

        // Read text from stream
        //return response.text();
        return response.json();
      })
      .then((responseJson) => {
        if (responseJson === null) {
          //console.log("No revisions found")
          
		  //toast.warning("No revisions found")
		  return
        }

        if (responseJson.success === false) {
          console.log("Error getting workflow revisions: ", responseJson)
          return
        }

        setAllRevisions(responseJson)
        setSelectedVersion(responseJson[0])
      })
      .catch((error) => {
        console.log("Error getting workflow revisions: ", error);
        ++turn;
        if (turn < 2) {
          getRevisionHistory(workflow_id, 5, turn, orgId);
        }
      });
  }

  const loadTriggers = (orgId) => {
    const url = `${globalUrl}/api/v1/triggers`

	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id !== "") {
		headers["Org-Id"] = workflow.org_id
	}

	if (orgId !== undefined && orgId !== null && orgId !== "") {
		headers["Org-Id"] = orgId
	}

    fetch(url,
      {
        method: "GET",
        headers: headers,
        credentials: "include",
      }
    )
      .then((response) => {
        if (response.status !== 200) {
          throw new Error("No folders :o!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success !== false) {
          	setAllTriggers(responseJson)
        } else {
			//toast.error("Failed to get triggers")
		}
      })
      .catch((error) => {
        console.log("Get outlook folders error: ", error.toString());
      });
  }

  const setupWorkflowYaml = (inputworkflow) => {
	  toast.warn("YAML exploring is an experimental feature - only visible to support users. The goal of this is to make it EASY to edit the workflow as YAML instead of just using the UI")
	  if (userdata?.support !== true) {
		  console.log("Not support: ", userdata)
		  return
	  }

	  // This should be getting the workflow based on actual nodes in the workflow
	  // as the goal is to have it be live
	  var copiedWorkflow = JSON.parse(JSON.stringify(inputworkflow))

	  const removeObjects = [
		  "execution_org",
		  "categories",
		  "example_argument",
		  "public",
		  "contact_info",
		  "published_id",
		  "revision_id",
		  "usecase_ids",
		  "input_questions",
		  "form_control",
		  "blogpost",
		  "video",
		  "status",
		  "generated",
		  "hidden",
		  "updated_by",
		  "validated",
		  "validation",
		  "childorg_workflow_ids",
		  "backup_config",
		  "auth_groups",
		  "isValid",
		  "workflow_as_code",
		  "image",
		  "sharing",
		  "owner",
		  "configuration",
		  "created",
		  "edited",
		  "last_runtime",
		  "due_date",
		  "is_valid",
		  "execution_environment",
		  "default_return_value",
		  "visual_branches",
		  "previously_saved",
		  "workflow_type",
		  "parentorg_workflow",
		  "suborg_distribution",
		  "id",
		  "comments",
		  "org_id",

		  // Failing test
		  "spalabi",
	  ]

	  for (var i = 0; i < removeObjects.length; i++) {
		  delete copiedWorkflow[removeObjects[i]]
	  }

	  const removeActionValues = [
		  "_id",
		  "id",

		  "large_image",
		  "description",
		  "is_valid",
		  "isStartNode",
		  "sharing",
		  "public",
		  "generated",
		  "execution_variable",
		  "position",
		  "category",
		  "reference_url",
		  "sub_action",
		  "run_magic_output",
		  "run_magic_input",
		  "category_label",
		  "suggestions",
		  "parent_controlled",
		  "source_workflow",
		  "source_executions",
		  "app_association",
		  "suggestion",
		  "small_image",
		  "long_description",
		  "tags",
		  "errors",
		  "source_executions",
		  "source_execution",
		  "required",
		  "example",
		  "type",

		  "test2",
	  ]

	  const removeActionParamValues = [
		  "id",
		  "multiline",
		  "multiselect",
		  "options",
		  "action_field",
		  "variant",
		  "configuration",
		  "tags",
		  "schema",
		  "skip_multicheck",
		  "value_replace",
		  "unique_toggled",
		  "hidden",
		  "error",
		  "example",

		  "test3",
	  ]

	  if (copiedWorkflow?.actions !== undefined && copiedWorkflow?.actions !== null && copiedWorkflow?.actions.length > 0) {
		  for (var key in copiedWorkflow.actions) {
			  for (var i = 0; i < removeActionValues.length; i++) {
				  delete copiedWorkflow.actions[key][removeActionValues[i]]
			  }


			  if (copiedWorkflow.actions[key].parameters === undefined || copiedWorkflow.actions[key].parameters === null) {
				  continue
			  }

			  for (var j = 0; j < copiedWorkflow.actions[key].parameters.length; j++) {
				  for (var k = 0; k < removeActionParamValues.length; k++) {
					  delete copiedWorkflow.actions[key].parameters[j][removeActionParamValues[k]]
				  }
			  }
		  }
	  }

	  if (copiedWorkflow?.triggers !== undefined && copiedWorkflow?.triggers !== null && copiedWorkflow?.triggers.length > 0) {
		  for (var key in copiedWorkflow.triggers) {
			  for (var i = 0; i < removeActionValues.length; i++) {
				  delete copiedWorkflow.triggers[key][removeActionValues[i]]
			  }

			  delete copiedWorkflow.triggers[key]["app_name"]
			  delete copiedWorkflow.triggers[key]["app_version"]
			  delete copiedWorkflow.triggers[key]["name"]
			  delete copiedWorkflow.triggers[key]["priority"]
			  delete copiedWorkflow.triggers[key]["replacement_for_trigger"]

			  if (copiedWorkflow.triggers[key].parameters === undefined || copiedWorkflow.triggers[key].parameters === null) {
				  continue
			  }

			  for (var j = 0; j < copiedWorkflow.triggers[key].parameters.length; j++) {
				  for (var k = 0; k < removeActionParamValues.length; k++) {
					  delete copiedWorkflow.triggers[key].parameters[j][removeActionParamValues[k]]
				  }
			  }
		  }
	  }

	  if (copiedWorkflow?.workflow_variables === undefined || copiedWorkflow?.workflow_variables === null || copiedWorkflow?.workflow_variables.length === 0) {
		  delete copiedWorkflow.workflow_variables
	  }

	  if (copiedWorkflow?.branches === undefined || copiedWorkflow?.branches === null || copiedWorkflow?.branches.length === 0) {
		  delete copiedWorkflow.branches
	  }


	  // YAML
	  const sampledata = YAML.stringify(copiedWorkflow)

	  navigate("?ui=yaml", { replace: true })

      setCodeEditorModalOpen(true) 
	  setEditorData({
		"name": "workflow yaml",
		"value": sampledata,
	  })
  }

  // eslint-disable-next-line react-hooks/exhaustive-deps
  //useEffect(() => {
  if (firstrequest) {
    setFirstrequest(false)
    getWorkflow(props.match.params.key, {})
    getRevisionHistory(props.match.params.key)
    loadTriggers()
    getApps()
    fetchUsecases()

    getWorkflowExecution(props.match.params.key, "", executionFilter)

    setLeftSideBarOpenByClick(false)
    localStorage.setItem("expandLeftNav", false)

    // FIXME: Don't check specific one here
    const tmpExec = new URLSearchParams(cursearch).get("execution_highlight");
    if (
      tmpExec !== undefined &&
      tmpExec !== null &&
      tmpExec === "executions"
    ) {
      setExecutionModalOpen(true)
      const newitem = removeParam("execution_highlight", cursearch);
      navigate(curpath + newitem)
      //props.history.push(curpath + newitem);
    }

    const tmpView = new URLSearchParams(cursearch).get("view");
    if (
      tmpView !== undefined &&
      tmpView !== null &&
      tmpView === "executions"
    ) {
      setExecutionModalOpen(true);

      const newitem = removeParam("view", cursearch);
      navigate(curpath + newitem)
      //navigate(`?execution_highlight=${parsed_url}`)
      //props.history.push(curpath + newitem);
    }

    return;
  }

  // App length necessary cus of cy initialization
  // Not using recommendations, so skipping this for now
  //if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0 && workflowRecommendations !== undefined) {
  if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
    setGraphSetup(true);
    setupGraph(workflow);
    console.log("In graph setup")

    // 2nd load - configures cytoscape
    //} else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined && workflowRecommendations !== undefined) {
  } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined) {

    //This part has to load LAST, as it's kind of not async.
    //This means we need everything else to happen first.

    setEstablished(true);
    // Validate if the node is just a node lol

    // https://www.npmjs.com/package/cytoscape-grid-guide
    //
    if (cy.gridGuide !== undefined) {
      cy.gridGuide({
        gridSpacing: 30,
        guidelinesStyle: {
          strokeStyle: "#8b7d6b", 					// color of geometric guidelines
          geometricGuidelineRange: 400, 		// range of geometric guidelines
          range: 100, 											// max range of distribution guidelines
          minDistRange: 10, 								// min range for distribution guidelines
          distGuidelineOffset: 10, 					// shift amount of distribution guidelines
          horizontalDistColor: "#ff0000", 	// color of horizontal distribution alignment
          verticalDistColor: "#00ff00", 		// color of vertical distribution alignment
          initPosAlignmentColor: "#0000ff", // color of alignment to initial mouse location
          lineDash: [0, 0], 								// line style of geometric guidelines
          horizontalDistLine: [0, 0], 			// line style of horizontal distribution guidelines
          verticalDistLine: [0, 0], 				// line style of vertical distribution guidelines
          initPosAlignmentLine: [0, 0], 		// line style of alignment to initial mouse position
        }
      })
    } else {
      console.log("ERROR: Failed to render grid as it's unitialized")
    }

    if (cy.edgehandles !== undefined) {
      cy.edgehandles({
        handleNodes: (el) => {
          // Check of length of el.data() is 1
          if (el.data() === undefined || Object.keys(el.data()).length === 1) {
            return false
          }

          if (el.isNode() &&
            el.data("buttonType") != "ACTIONSUGGESTION" &&
            el.data("name") != "switch" &&
            !el.data("isButton") &&
            !el.data("isDescriptor") &&
            !el.data("isSuggestion") &&
            el.data("type") !== "COMMENT" &&
            el.data("type") !== "RESIZE-HANDLE") {
            return true
          }

          return false
        },
        handlePosition: (node) => {
          const orientation = node.data("flowOrientation");
          return orientation === "vertical" ? "middle top" : "right middle";
        },
        preview: false,
        toggleOffOnLeave: true,
        loopAllowed: function (node) {
          return false;
        },
      })

      //cy.edgehandles({
      //	preview: false,
      //})

      //cy.edgehandles().enable()
    } else {
      console.log("ERROR: Failed to initialize edgehandler")
    }
    // preview: true,

	try {
    	cy.fit(null, 400)
	} catch (error) {
		console.log("Error fitting cytoscape (4): ", error)
	}

    // Ensure all nodes have correct initial orientation based on edges
    // after graph is rendered / fitted.
    try {
      cy.nodes().forEach((node) => {
        if (node.data("flowOrientation") === undefined) {
          node.data("flowOrientation", "horizontal")
        }

        updateNodeLabelOrientation(node)
      })
    } catch (error) {
      console.log("Error setting initial flowOrientation: ", error)
    }

    cy.on("boxselect", "node", (e) => {
	  e.preventDefault()
	  e.stopPropagation()

	  console.log("BOXSELECT: ", e.boxSelectElements)
      if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) {
        e.target.unselect();
      }

      e.target.addClass("selected");
    });

    cy.on("boxstart", (e) => {
      console.log("START");
	  e.preventDefault()
	  e.stopPropagation()
    });

    cy.on("boxend", (e) => {
	  e.preventDefault()
	  e.stopPropagation()

      console.log("END: ", e.target, cy)
      var cydata = cy.$(":selected").jsons();
      if (cydata !== undefined && cydata !== null && cydata.length > 0) {
		// Unselect all nodes
		cy.$(":selected").unselect()
        toast(`Selected ${cydata.length} element(s). CTRL+C to copy them.`);
      }
    });

    cy.on('grab', 'edge', (e) => {
      console.log("Edge grabbed: ", e.target.data())
    })

    cy.on("select", "node", (e) => {
      onNodeSelect(e, appAuthentication);
    });
    cy.on("select", "edge", (e) => onEdgeSelect(e));

    cy.on("unselect", (e) => onUnselect(e));

    cy.on("add", "node", (e) => onNodeAdded(e));
    cy.on("add", "edge", (e) => onEdgeAdded(e));
    cy.on("remove", "node", (e) => onNodeRemoved(e));
    cy.on("remove", "edge", (e) => onEdgeRemoved(e));

    cy.on("mouseover", "edge", (e) => onEdgeHover(e));
    cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e));
    cy.on("mouseover", "node", (e) => onNodeHover(e));
    cy.on("mouseout", "node", (e) => onNodeHoverOut(e));

    cy.on("mouseover", "node[type='RESIZE-HANDLE']", (e) => {
      const nodeId = e.target.id();
      
      // Check the node ID to determine the cursor style based on position
      if (nodeId.includes("bottom-right")) {
        cy.container().style.cursor = "nwse-resize"; // Bottom-right resize cursor
      } else if (nodeId.includes("top-right")) {
        cy.container().style.cursor = "nesw-resize"; // Top-right resize cursor
      } else if (nodeId.includes("top-left")) {
        cy.container().style.cursor = "nwse-resize"; // Top-left resize cursor
      } else if (nodeId.includes("bottom-left")) {
        cy.container().style.cursor = "nesw-resize"; // Bottom-left resize cursor
      } else {
        cy.container().style.cursor = "default"; // Default cursor for other cases
      }
    });

    cy.on("mouseout", "node[type='RESIZE-HANDLE']", (e) => {
      cy.container().style.cursor = "";
    });

    // Handles dragging
    cy.on("drag", "node", (e) => {
      const node = e.target;
      updateNodeAndNeighborsOrientation(node);

      onNodeDrag(e, selectedAction);
    });

    cy.on("free", "node", (e) => {
      const node = e.target;
      updateNodeAndNeighborsOrientation(node);

      onNodeDragStop(e, selectedAction);
    });

    cy.on("cxttap", "node", (e) => onCtxTap(e));

    document.title = "Workflow - " + workflow.name;

    startWorkflowStream(props.match.params.key);

    registerKeys();
  }
  //})

  const stopSchedule = (trigger, triggerindex) => {
	if (cy !== undefined && cy !== null) {
		cy.$(":selected").unselect()
	}

	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id !== "") {
		headers["Org-Id"] = workflow.org_id
	}

    fetch(
      `${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule/${trigger.id}`,
      {
        method: "DELETE",
        headers: headers, 
        credentials: "include",
      }
    )
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for stream results :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        // No matter what, it's being stopped.
        if (!responseJson.success) {
          if (responseJson.reason !== undefined) {
            toast("Failed to stop schedule: " + responseJson.reason);
          }
        } else {
          toast("Successfully stopped schedule");
        }

		if (triggerindex !== undefined && triggerindex !== null && triggerindex >= 0) {
        	workflow.triggers[triggerindex].status = "stopped";
		}

        //trigger.status = "stopped";
		//console.log("TRIGGER: ", trigger)
        //setSelectedTrigger(trigger);

        setWorkflow(workflow);
        saveWorkflow(workflow)

  		loadTriggers(workflow.org_id)
      })
      .catch((error) => {
        console.log("Stop schedule error: ", error.toString())
      })
  }

  const submitPipeline = (trigger, triggerindex, usecase) => {
    if (trigger.name.length <= 0) {
      toast("Error: name can't be empty");
      return;
    }

    var mappedStartnode = "";
    const alledges = cy.edges().jsons();
    if (alledges !== undefined && alledges !== null && alledges.length > 0) {
      for (let edgekey in alledges) {
        const tmp = alledges[edgekey];
        if (tmp.data.source === trigger.id) {
          mappedStartnode = tmp.data.target;
          break;
        }
      }
    }
    const data = usecase;
    data.start_node = mappedStartnode

    const url = `${globalUrl}/api/v1/triggers/pipeline`;
    fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(data),
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for stream results :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (!responseJson.success && data.type !== "delete") {
          toast("Failed to set pipeline: " + responseJson.reason);
        } else {
          if (data.type === "create") {
            toast("Pipeline will be created!");
          } else if (data.type === "stop") {
            toast("Pipeline will be stopped!");
          } else {
            toast("Pipeline deleted!")
            return
          }
          if (trigger.parameters) {
            trigger.parameters.push({
              name: data.name,
              value: data.command,
            });
          }

          if (data.type === "stop") trigger.status = "stopped";
          else trigger.status = "running";
          workflow.triggers[triggerindex] = trigger;

          setSelectedTrigger(trigger);
          setWorkflow(workflow);
          saveWorkflow(workflow);
        }
      })
      .catch((error) => {
        console.log("Get pipeline error: ", error.toString());
      });
  };

  const submitSchedule = (trigger, triggerindex) => {
    if (trigger.name.length <= 0) {
      toast("Error: name can't be empty");
      return;
    }

    var mappedStartnode = ""
    const alledges = cy.edges().jsons()
    if (alledges !== undefined && alledges !== null && alledges.length > 0) {
      for (let edgekey in alledges) {
        const tmp = alledges[edgekey]
        if (tmp.data.source === trigger.id) {
          mappedStartnode = tmp.data.target
          break
        }
      }
    }

    toast.info("Creating schedule")
    var data = {
      name: trigger.name,
      frequency: workflow.triggers[triggerindex].parameters[0].value,
      execution_argument: workflow.triggers[triggerindex].parameters[1].value,
      environment: workflow.triggers[triggerindex].environment,
      id: trigger.id,
      start: mappedStartnode,
    }

    if (data.frequency === undefined || data.frequency === null || data.frequency.length === 0) {
      if (isCloud || selectedTrigger?.environment === "cloud") {
        data.frequency = "*/25 * * * *"
        workflow.triggers[triggerindex].parameters[0].value = "*/25 * * * *"
      } else {
        data.frequency = "60"
        workflow.triggers[triggerindex].parameters[0].value = "60"
      }

      setWorkflow(workflow)
    }

	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id !== "") {
		headers["Org-Id"] = workflow.org_id
	}

    fetch(
      `${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule`,
      {
        method: "POST",
        headers: headers,
        body: JSON.stringify(data),
        credentials: "include",
      }
    )
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for stream results :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (!responseJson.success) {
          toast.error("Failed to set schedule: " + responseJson.reason);
        } else {
          toast.success("Successfully created schedule");
          workflow.triggers[triggerindex].status = "running";
          trigger.status = "running";
          setSelectedTrigger(trigger);
          setWorkflow(workflow);
          saveWorkflow(workflow);

  		  loadTriggers(workflow.org_id)
        }
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Get schedule error: ", error.toString());
      });
  };

  const getSigmaInfo = () => {
    const url = globalUrl + "/api/v1/files/detection/sigma_rules";

    fetch(url, {
      method: "GET",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
      },
    })
      .then((response) =>
        response.json().then((responseJson) => {
          if (responseJson["success"] === false) {
            toast("Failed to get sigma rules");
          } else {
            setRules(responseJson.sigma_info);

          }
        })
      )
      .catch((error) => {
        console.log("Error in getting sigma files: ", error);
        toast("An error occurred while fetching sigma rules");
      });
  };

  const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - 57
  const appViewStyle = {
    marginLeft: 5,
    marginRight: 5,
    display: "flex",
    backgroundColor: theme.palette.backgroundColor,
    flexDirection: "column",
    minHeight: isMobile ? bodyHeight - appBarSize * 4 : parsedHeight,
    maxHeight: isMobile ? bodyHeight - appBarSize * 4 : parsedHeight,
  };

  const paperAppStyle = {
    borderRadius: theme.palette?.borderRadius,
    minHeight: isMobile ? 50 : 55,
    maxHeight: isMobile ? 50 : 55,
    minWidth: isMobile ? 50 : "100%",
    maxWidth: isMobile ? 50 : "100%",
    marginTop: "5px",
    color: theme.palette.text.primary,
    backgroundColor: theme.palette.surfaceColor,
    cursor: "pointer",
    display: "flex",
  };

  const paperVariableStyle = {
    borderRadius: theme.palette?.borderRadius,
    minHeight: 70,
    maxHeight: 150,
    minWidth: "100%",
    maxWidth: "100%",
    marginTop: "5px",
    color: theme.palette.text.primary,
    backgroundColor: theme.palette.surfaceColor,
    cursor: "pointer",
    display: "flex",
  }

  const VariableItem = (props) => {
    const { variable, index, type } = props;

    const [open, setOpen] = React.useState(false);
    const [anchorEl, setAnchorEl] = React.useState(null);

    const menuClick = (event) => {
      setOpen(!open);
      setAnchorEl(event.currentTarget);
    };

    const deleteVariable = (type, variableIndex) => {

      console.log("Delete type: ", type, variableIndex)

      if (type === "normal") {
        if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length > variableIndex) {
          var vars = JSON.parse(JSON.stringify(workflow.workflow_variables))
          vars.splice(variableIndex, 1)
          workflow.workflow_variables = vars

          console.log("Workflow after del: ", workflow)

          setWorkflow(workflow);
          setUpdate(Math.random());
        }
      } else if (type === "exec") {
        if (workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > variableIndex) {
          var vars = JSON.parse(JSON.stringify(workflow.execution_variables))
          vars.splice(variableIndex, 1)
          workflow.execution_variables = vars

          console.log("Workflow after del: ", workflow)

          setWorkflow(workflow);
          setUpdate(Math.random());
        }
      }
    };

    return (
      <div key={index}>
        <Paper square style={paperVariableStyle} onClick={() => { }}>
          <div
            style={{
              marginLeft: "10px",
              marginTop: "5px",
              marginBottom: "5px",
              width: 2,
              backgroundColor: yellow,
              marginRight: "5px",
            }}
          />
          <div style={{ display: "flex", width: "100%" }}>
            <div
              style={{
                flex: "10",
                marginTop: "15px",
                marginLeft: "10px",
                overflow: "hidden",
              }}
              onClick={() => {
                setVariableInfo({
                  "name": variable.name,
                  "description": variable.description,
                  "value": variable.value,
                  "index": index,
                })

                if (type === "normal") {
                  setVariablesModalOpen(true);
                } else if (type === "exec") {
                  setExecutionVariablesModalOpen(true);
                } else {
                  console.log("Unknown type: ", type)
                }
              }}
            >
              {variable.name}
            </div>
            <div style={{ flex: "1", marginLeft: "0px" }}>
              <IconButton
                aria-label="more"
                aria-controls="long-menu"
                aria-haspopup="true"
                onClick={menuClick}
                style={{ color: theme.palette.text.primary }}
              >
                <MoreVertIcon />
              </IconButton>
              <Menu
                id="long-menu"
                anchorEl={anchorEl}
                keepMounted
                open={open}
                PaperProps={{
                  style: {
                    backgroundColor: theme.palette.surfaceColor,
                  },
                }}
                onClose={() => {
                  setOpen(false);
                  setAnchorEl(null);
                }}
              >
                <MenuItem
                  style={{
                    backgroundColor: theme.palette.inputColor,
                    color: theme.palette.text.primary,
                  }}
                  onClick={() => {
                    setOpen(false);
                    setVariableInfo({
                      "name": variable.name,
                      "description": variable.description,
                      "value": variable.value,
                      "index": index,
                    })

                    if (type === "normal") {
                      setVariablesModalOpen(true);
                    } else if (type === "exec") {
                      setExecutionVariablesModalOpen(true);
                    } else {
                      console.log("Unknown type: ", type)
                    }
                  }}
                  key={"Edit"}
                >
                  {"Edit"}
                </MenuItem>
                <MenuItem
                  style={{
                    backgroundColor: theme.palette.inputColor,
                    color: theme.palette.text.primary,
                  }}
                  onClick={() => {
                    deleteVariable(type, index);
                    setOpen(false);
                  }}
                  key={"Delete"}
                >
                  {"Delete"}
                </MenuItem>
              </Menu>
            </div>
          </div>
        </Paper>
      </div>
    )
  }

  const VariablesView = () => {
    const variableScrollStyle = {
      margin: 15,
      overflow: "scroll",
      height: isMobile ? "100%" : "66vh",
      overflowX: "auto",
      overflowY: "auto",
      flex: "10",
    };

    return (
      <div style={appViewStyle}>
        <div style={variableScrollStyle}>
          What are{" "}
          <a
            rel="noopener noreferrer"
            href="https://shuffler.io/docs/workflows#workflow_variables"
            target="_blank"
            style={{ textDecoration: "none", color: theme.palette.linkColor }}
          >
            Workflow variables?
          </a>
          {workflow.workflow_variables === null
            ? null
            : workflow.workflow_variables.map((variable, varindex) => {
              return (
                <VariableItem
                  variable={variable}
                  index={varindex}
                  type={"normal"}
                />
              );
            })}

          <div style={{ flex: "1" }}>
            <Button
              fullWidth
              style={{ margin: "auto", marginTop: "10px" }}
              color="primary"
              variant="outlined"
              onClick={() => {
                setVariablesModalOpen(true);
                setLastSaved(false)

                setVariableInfo({
                  "name": "",
                  "description": "",
                  "value": "",
                })
              }}
            >
              New workflow variable
            </Button>
          </div>
          <Divider
            style={{
              marginBottom: 20,
              marginTop: 20,
              height: 1,
              width: "100%",
              backgroundColor: "rgb(91, 96, 100)",
            }}
          />
          What are{" "}
          <a
            rel="noopener noreferrer"
            href="https://shuffler.io/docs/workflows#execution_variables"
            target="_blank"
            style={{ textDecoration: "none", color: theme.palette.linkColor }}
          >
            Runtime variables?
          </a>
          {workflow.execution_variables === null ||
            workflow.execution_variables === undefined
            ? null
            : workflow.execution_variables.map((variable, varindex2) => {
              return (
                <VariableItem
                  variable={variable}
                  index={varindex2}
                  type={"exec"}
                />
              );
            })}
          <div style={{ flex: "1" }}>
            <Button
              fullWidth
              style={{ margin: "auto", marginTop: "10px" }}
              color="primary"
              variant="outlined"
              onClick={() => {
                setExecutionVariablesModalOpen(true);
                setLastSaved(false);

                setVariableInfo({
                  "name": "",
                  "description": "",
                  "value": "",
                })
              }}
            >
              New Runtime variable
            </Button>
          </div>
        </div>
      </div>
    );
  };



  const HandleLeftView = () => {
    // console.log("HandleLeftView Rendered!")

    const handleSetTab = (event, newValue) => {
      setCurrentView(newValue);
    };

    // Defaults to apps.
    var thisview = (
      <AppView
        allApps={apps}
        extraApps={[]}
        prioritizedApps={prioritizedApps}
        filteredApps={filteredApps}
      />
    );
    if (currentView === 1) {
      thisview = <TriggersView />;
    } else if (currentView === 2) {
      thisview = <VariablesView />;
    }

    const tabStyle = {
      maxWidth: isMobile ? leftBarSize : leftBarSize / 2,
      minWidth: isMobile ? leftBarSize : leftBarSize / 2,
      flex: 1,
      textTransform: "none",
    };

    const iconStyle = {
      marginTop: 3,
      marginRight: 5,
    };

    return (
      <div>
        <div
          style={{
            minHeight: parsedHeight,
            maxHeight: parsedHeight,
            overflow: "hidden",
          }}
        >
          {thisview}
        </div>
        <Divider style={{ backgroundColor: "rgb(91, 96, 100)", }} />
        <Tabs
          value={currentView}
          indicatorColor="primary"
          onChange={handleSetTab}
          aria-label="Left sidebar tab"
          orientation={isMobile ? "vertical" : "horizontal"}
          style={{
            display: "flex",
            width: "100%",
          }}
        >
          <Tab
            value={0}
            label={
              "Apps"
            }
            style={tabStyle}
          />
          {/*
          <Tab
			value={1}
            label={
              <Grid container direction="row" alignItems="center">
                <Grid item>
                  <ScheduleIcon style={iconStyle} />
                </Grid>
                {isMobile ? null : <Grid item>Triggers</Grid>}
              </Grid>
            }
            style={{
			  maxWidth: isMobile ? leftBarSize : leftBarSize / 3,
			  minWidth: isMobile ? leftBarSize : leftBarSize / 3,
			  flex: 1,
			  textTransform: "none",
			  padding: 0, 
			}}
          />
		  */}
          <Tab
            value={2}
            label={
              "Vars"
            }
            style={tabStyle}
          />
        </Tabs>
      </div>
    )
  }



  const TriggersView = () => {
    const triggersViewStyle = {
      marginLeft: 10,
      marginRight: 10,
      display: "flex",
      flexDirection: "column",
    }

    // Predefined hurr
    return (
      <div style={triggersViewStyle}>
        <div style={appScrollStyle}>
          {triggers.map((trigger, index) => {

            // Hiding since March 2024
            if (trigger.trigger_type === "EMAIL") {
              return null
            }

            if (trigger.trigger_type == "PIPELINE") {
              return null
            }

            const imagesize = isMobile ? 40 : trigger.large_image.includes("svg") ? 50 : 50
            var imageline = trigger.large_image.length === 0 ? <img alt="" style={{ borderRadius: theme.palette?.borderRadius, width: isMobile ? 40 : 60, pointerEvents: "none" }} />
              :
              <img
                alt=""
                src={trigger.large_image}
                style={{
                  borderRadius: theme.palette?.borderRadius,
                  width: imagesize,
                  height: imagesize,
                  // Stretch if necessary
                  objectFit: "cover",
                  // Center the object
                  objectPosition: "center center",
                }}
              />

            const title = trigger.trigger_type === "WEBHOOK" ? "Workflow starters" : trigger.trigger_type === "SUBFLOW" ? "Mid-Workflow" : ""

            const color = trigger.is_valid ? green : yellow;
            return (
              <span>
                {title.length > 0 ?
                  <Typography variant="h6" style={{ marginTop: 10, marginBottom: 10, marginLeft: 10, }}>
                    {title}
                  </Typography>
                  : null}

                <Draggable
                  key={index}
                  onDrag={(e) => {
                    handleTriggerDrag(e, trigger);
                  }}
                  onStop={(e) => {
                    handleDragStop(e);
                  }}
                  dragging={false}
                  position={{
                    x: 0,
                    y: 0,
                  }}
                >
                  <Paper square style={paperAppStyle} onClick={() => { }}>
                    <div
                      style={{
                        marginLeft: isMobile ? 0 : 10,
                        marginTop: isMobile ? 10 : 5,
                        marginBottom: 5,
                        width: 2,
                        backgroundColor: color,
                        marginRight: 5,
                      }}
                    ></div>
                    <Grid
                      container
                      style={{ margin: isMobile ? "10px 0px 0px 0px" : "10px 10px 10px 10px", flex: "10", overflow: "hidden", }}
                    >
                      <Grid item>
                        <ButtonBase>{imageline}</ButtonBase>
                      </Grid>
                      {isMobile ? null :
                        <Grid
                          style={{
                            display: "flex",
                            flexDirection: "column",
                            marginLeft: 20,
                          }}
                        >
                          <Grid item style={{ flex: "1", overflow: "hidden", }}>
                            <Typography variant="body1" style={{ marginTop: 0, marginBottom: 0, overflow: "hidden" }}>
                              {trigger.name}
                            </Typography>
                          </Grid>
                          <Grid item style={{ flex: "1" }}>
                            <Typography variant="body2" color="textSecondary" style={{ marginTop: 0, marginBottom: 0, overflow: "hidden", }}>
                              {trigger.description}
                            </Typography>
                          </Grid>
                        </Grid>
                      }
                    </Grid>
                  </Paper>
                </Draggable>
              </span>
            );
          })}
        </div>
      </div>
    );
  };

  var newNodeId = "";
  var parsedApp = {};
  const handleTriggerDrag = (e, data) => {
    const cycontainer = cy.container();
    // Chrome lol
    if (
      e.pageX > cycontainer.offsetLeft &&
      e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth &&
      e.pageY > cycontainer.offsetTop &&
      e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight
    ) {
      if (newNodeId.length > 0) {
        var currentnode = cy.getElementById(newNodeId);
        if (currentnode.length === 0) {
          return;
        }

        currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft);
        currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop);
      } else {
        if (workflow.start === "" || workflow.start === undefined) {
          toast("Define a starting action first.");
          return;
        }

        const triggerLabel = getNextActionName(data.name);

        newNodeId = uuidv4();
        const newposition = {
          x: e.pageX - cycontainer.offsetLeft,
          y: e.pageY - cycontainer.offsetTop,
        };

        var newAppData = {
          app_name: data.name,
          app_version: "1.0.0",
          environment: isCloud ? "cloud" : data.environment,
          description: data.description,
          long_description: data.long_description,
          errors: [],
          id_: newNodeId,
          _id_: newNodeId,
          id: newNodeId,
          finished: false,
          label: triggerLabel,
          type: data.type,
          is_valid: true,
          trigger_type: data.trigger_type,
          large_image: data.large_image,
          status: "uninitialized",
          name: data.name,
          parameters: data?.parameters,
          isStartNode: false,
          position: newposition,
        }

        if (data.name === "Pipelines") {
          var newenv = environments.find((env) => {
            if (env.archived) {
              return false
            }

            if (env.Name === "cloud" || env.Type === "cloud") {
              return false
            }

            return true
          })

          if (newenv !== undefined && newenv !== null) {
            newAppData.environment = newenv.Name
          } else {
            newAppData.environment = ""
          }
        }

        // Can all the data be in here? hmm
        const nodeToBeAdded = {
          group: "nodes",
          data: newAppData,
          renderedPosition: newposition,
        };

        cy.add(nodeToBeAdded);
        parsedApp = nodeToBeAdded;
        return;
      }
    }
  };

  const handleDragStop = (e, app) => {
	if (cy === undefined || cy == null) {
		console.log("Cytoscape not initialized")
		return
	}

    var currentnode = cy.getElementById(newNodeId);

    if (currentnode === undefined || currentnode === null || currentnode.length === 0) {
		console.log("No current node found")
      	return
    }

    if (parsedApp === undefined || parsedApp === null || parsedApp.data === undefined || parsedApp.data === null) {
      toast("Failed to add node. Please try again.")
      console.log("Failed to add node. Please try again. Parsed app:", parsedApp)
      return
    }


    // Using remove & replace, as this triggers the function
    // onNodeAdded() with this node after it's added
    //console.log("DRAG STOP 3: ", parsedApp.data)
    currentnode.remove()

    parsedApp.data.finished = true
    parsedApp.data.position = currentnode.renderedPosition()
    parsedApp.position = currentnode.renderedPosition()
    parsedApp.renderedPosition = currentnode.renderedPosition()

    var newAppData = JSON.parse(JSON.stringify(parsedApp.data))
    if (newAppData.type === "ACTION") {

      if (newAppData.app_name === "Shuffle Tools") {
        const iconInfo = GetIconInfo(newAppData)
        const iconViewBox = iconInfo.svgViewBox || `0 0 ${svgSize} ${svgSize}`;
        const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="${iconViewBox}" version="1.1" xmlns="http://www.w3.org/2000/svg">${buildIconSvgPath(iconInfo, "white")}</svg>`;
        const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
        newAppData.large_image = svgpin_Url

        newAppData.fillGradient = iconInfo.fillGradient
        newAppData.fillstyle = "solid"
        if (
          newAppData.fillGradient !== undefined &&
          newAppData.fillGradient !== null &&
          newAppData.fillGradient.length > 0
        ) {
          newAppData.fillstyle = "linear-gradient"
        } else {
          newAppData.iconBackground = iconInfo.iconBackgroundColor
        }
      }


      //const activateApp = (appid) => {
      if (newAppData.activated === false) {
        activateApp(newAppData.app_id, false)
      }

      // AUTHENTICATION
      if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) {

        // Setup auth here :)
        const authenticationOptions = [];
        var findAuthId = "";
        if (
          newAppData.authentication_id !== null &&
          newAppData.authentication_id !== undefined &&
          newAppData.authentication_id.length > 0
        ) {
          findAuthId = newAppData.authentication_id;
        }

        const tmpAuth = JSON.parse(JSON.stringify(appAuthentication));
        for (let authkey in tmpAuth) {
          if (authkey === undefined) {
            continue
          }

          var item = tmpAuth[authkey];
          const newfields = {};
          for (let fieldkey in item.fields) {
            if (item.fields[fieldkey] === undefined) {
              console.log("Problem with filterkey in Node select", fieldkey)
              continue
            }

            const filterkey = item.fields[fieldkey]["key"]
            if (filterkey === null || filterkey === undefined) {
              console.log("Problem with filterkey 2. Null or undefined 3")
              continue
            }

            newfields[filterkey] = item.fields[fieldkey]["value"];
          }

          item.fields = newfields;
          if (item.app.id === app.id || item.app.name === app.name) {
            authenticationOptions.push(item);

            if (item.id === findAuthId) {
              newAppData.selectedAuthentication = item
              newAppData.authentication_id = item.id

            } else if (findAuthId === "") {
              // Will always be set to the last one if one isn't found. 
              // Last = timestamp too
              newAppData.selectedAuthentication = item
              newAppData.authentication_id = item.id
            }
          }
        }

        if (
          authenticationOptions !== undefined &&
          authenticationOptions !== null &&
          authenticationOptions.length > 0
        ) {
          for (let authkey in authenticationOptions) {
            const option = authenticationOptions[authkey];

            if (option.active && newAppData.authentication_id === "") {
              newAppData.selectedAuthentication = option;
              newAppData.authentication_id = option.id;
              break;
            }
          }
        }
      } else {

        newAppData.authentication = [];
        newAppData.authentication_id = "";
        newAppData.selectedAuthentication = {};
      }

      /*
      if (workflow.actions === undefined || workflow.actions === null) {
        workflow.actions = []
      }
  
      for (var key in workflow.actions) {
        const action = workflow.actions[key]
        console.log("Action: ", action)
      }
      */


      parsedApp.data = newAppData;
      cy.add(parsedApp)
    } else if (newAppData.type === "TRIGGER") {
      cy.add(parsedApp)
    }

    newNodeId = ""
    parsedApp = {}
  };

  const barHeight = bodyHeight - appBarSize - 50;
  const appScrollStyle = {
    overflow: "scroll",
    maxHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight - 70,
    minHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight - 70,
    marginTop: 1,
    overflowY: "auto",
    overflowX: "hidden",

    zoom: 0.9,
  }

  const handleAppDrag = async (e, app) => {
	if (cy === undefined || cy === null) {
		return
	}

    const cycontainer = cy.container()

    // Handling drag of public apps
    if (app.objectID !== undefined && app.objectID !== null && app.objectID.length > 0) {
      // FIXME: This is still buggy for now. Not allowed
      return
      loadAppConfig(app.objectID, undefined)

      // Some arbitrary check

      app.id = app.objectID
      app.actions = [{
        "name": "tmp",
        "parameters": [{
          "name": "tmp",
        }]
      }]
    }

    if (app.type === "TRIGGER") {
      handleTriggerDrag(e, app)
      return
    }


    // HTML -> Canvas overlap check
    if (
		   e.pageX > cycontainer.offsetLeft 
		&& e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth && e.pageY > cycontainer.offsetTop 
		&& e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight
	) {
      if (newNodeId.length > 0) {
        var currentnode = cy.getElementById(newNodeId);
        if (currentnode === undefined || currentnode === null || currentnode.length === 0) {
          return
        }

		findClosestNode({
			target: currentnode, 
		}, currentnode.data())

        currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft)
        currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop)
      } else {
        if (workflow.public) {
          console.log("workflow is public - not adding")
          return;
        }

        if (app.actions === undefined || app.actions === null) {
          app.actions = []
        }

        /*
            if (app.actions === undefined || app.actions === null || app.actions.length === 0) {
              toast("App " + app.name + " currently has no actions to perform. Please go to https://shuffler.io/apps to edit it.")
    
              return
            }
        */

        newNodeId = uuidv4();
        const actionType = "ACTION";
        const actionLabel = getNextActionName(app.name);
        var parameters = null;
        var example = "";
        var description = ""

        const startIndex = app.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0)
        const actionIndex = startIndex < 0 ? 0 : startIndex

        if (app.actions[actionIndex] === undefined || app.actions[actionIndex] === null) {
          if (app.id !== undefined && app.id !== null) {
            loadAppConfig(app.id, false)
          }

          console.log("No actions found for app: ", app)
          return
        }

        // Make the first action the most relevant one for them based on previous use
        if (app.actions[actionIndex].parameters !== undefined && app.actions[actionIndex].parameters !== null && app.actions[actionIndex].parameters.length > 0) {

          parameters = JSON.parse(JSON.stringify(app.actions[actionIndex].parameters))
          for (let paramkey in parameters) {
            // Check if parameter.name == "headers" and if it includes "=undefined". If it does, set the value to example if it exists, otherwise empty
            if (parameters[paramkey].name === "headers" && parameters[paramkey].value.includes("=undefined")) {
              if (parameters[paramkey].example !== undefined && parameters[paramkey].example !== null && parameters[paramkey].example.length > 0) {
                parameters[paramkey].value = parameters[paramkey].example
              } else {
                parameters[paramkey].value = ""
              }
            }
          }
        }

        if (
          app.actions[actionIndex].returns !== undefined &&
          app.actions[actionIndex].returns !== null &&
          app.actions[actionIndex].returns.example !== undefined &&
          app.actions[actionIndex].returns.example !== null &&
          app.actions[actionIndex].returns.example.length > 0
        ) {
          example = app.actions[actionIndex].returns.example;
        }

        if (
          app.actions[actionIndex].description !== undefined &&
          app.actions[actionIndex].description !== null &&
          app.actions[actionIndex].description.length > 0
        ) {
          description = app.actions[actionIndex].description
        }

        var parsedEnvironments =
          environments === undefined || environments === null || environments === []
            ? isCloud ? "cloud" : "Shuffle" : environments[defaultEnvironmentIndex] === undefined
              ? isCloud ? "cloud" : "Shuffle" : environments[defaultEnvironmentIndex].Name

        // Basic automatic auth mapping
        var authId = ""
        if (appAuthentication !== undefined && appAuthentication !== null && appAuthentication.length > 0) {
          const appname = app.name.toLowerCase().replace(" ", "_")
          for (var key in appAuthentication) {
            const authKey = appAuthentication[key]
            if (authKey.app.id === app.id) {
              authId = authKey.id
              break
            }

            const appauthname = authKey.app.name.toLowerCase().replace(" ", "_")
            if (appauthname === appname) {
              authId = authKey.id
            }
          }
        }

        // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default
        if (cy !== undefined && cy !== null) {
          const foundnodes = cy.nodes().jsons()
          if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) {
            // As they should all be the same, this is just an override
            for (let nodekey in foundnodes) {
              const curnode = foundnodes[nodekey]
              if (curnode.data.environment !== undefined && curnode.data.environment !== null && curnode.data.environment.length > 0) {
                parsedEnvironments = curnode.data.environment
                break
              }
            }
          }
        }

        const originalBase64 = app.large_image !== undefined && app.large_image !== null && app.large_image !== "" ? app.large_image : app.palette.defaultImage
        const roundedImage = await roundBase64Image(originalBase64, 16);
      

        const newAppData = {
          name: app.actions[actionIndex].name,
          label: actionLabel,
          app_name: app.name,
          app_version: app.app_version,
          app_id: app.id,
          sharing: app.sharing,
          private_id: app.private_id,
          description: description,
          environment: parsedEnvironments,
          errors: [],
          id_: newNodeId,
          _id_: newNodeId,
          id: newNodeId,
          is_valid: true,
          type: actionType,
          parameters: parameters,
          isStartNode: false,
          large_image: roundedImage,
          run_magic_output: false,
          authentication: [],
          execution_variable: undefined,
          example: example,
          required_body_fields: app.actions[actionIndex].required_body_fields,
          category:
            app.categories !== null &&
              app.categories !== undefined &&
              app.categories.length > 0
              ? app.categories[0]
              : "",
          authentication_id: authId,
          template: app.template === true ? true : false,

          //finished: false,
          finished: false,
        }

        // FIXME: Something is going wrong with params
        if (!isCloud && (!app.is_valid || (!app.activated && app.generated))) {
          console.log("NOT VALID: Activate!")

          activateApp(app.id, false)
        }

        // find the cytoscape offset position
        // Can this be done with zoom calculations?
        const nodeToBeAdded = {
          group: "nodes",
          data: JSON.parse(JSON.stringify(newAppData)),
          renderedPosition: {
            x: e.pageX - cycontainer.offsetLeft,
            y: e.pageY - cycontainer.offsetTop,
          },
        }

        parsedApp = nodeToBeAdded
        cy.add(nodeToBeAdded)
        return
      }
    } else {
    }
  }

  const generateAIWorkflow = () => {
    const envToSend = selectedActionEnvironment?.Name || (isCloud ? "Cloud" : "Shuffle");

    const data = { 
      query: workflowDescription,
      workflow_id: props.match.params.key,
      environment: envToSend 
     };

    fetch(globalUrl + "/api/v2/workflows/generate/llm", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(data),
      credentials: "include",
    })
      .then(async (response) => {
        const json = await response.json();

        if (response.status !== 200) {
			if (json.reason !== undefined && json.reason !== null && json.reason.length > 0) {
				toast.error("Workflow generation failed: " + json.reason)
			}

			if (!isCloud) { 
				toast.info("Click here to set up a local LLM!", {
					autoClose: 10000,
					onClick: () => {
						window.open("/docs/AI#self-hosting-models", "_blank")
					}
				})
			}

          return null;
        }

        // AI “rejection” message (success: true + message)
        if (json.success === true && typeof json.message === "string") {
          toast(json.message);
          return null;
        }

        if (json.success === false) {
          toast(json.message || "Operation failed");
          return null;
        }

        if (!json || Object.keys(json).length === 0) {
          toast("Workflow generation failed: empty response");
          return null;
        }

        toast("Workflow generation successful");
        setWorkflow(json);
        return json;
      })
      .catch((error) => {
        console.error("AI Workflow Generation Error:", error);
        toast("Workflow generation failed due to network error");
      });
  };


  const AppView = (props) => {
    const { allApps, prioritizedApps, filteredApps, extraApps } = props;
    // console.log("AppView Rendered!")
    //extraApps,
    const [visibleApps, setVisibleApps] = React.useState(
      Array.prototype.concat.apply(
        prioritizedApps,
        Array.prototype.concat.apply(
          filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id.toLowerCase())),
        )
      )
    )

    var delay = -75
    var runDelay = false

    const ParsedAppPaper = (props) => {
      const app = props.app
      const small = props.small
      const actionString = props.action

      const [hover, setHover] = React.useState(false);

      // Prevent hover effects during drag
      const handleMouseMove = React.useCallback((e) => {
        if (dragRef.current) {
          setHover(false);
        }
      }, []);

      React.useEffect(() => {
        // Add mousemove listener to track dragging
        document.addEventListener('mousemove', handleMouseMove);
        return () => {
          document.removeEventListener('mousemove', handleMouseMove);
        };
      }, [handleMouseMove]);


      React.useEffect(() => {
        if (props.skip_load === true) {
          return
        }

        if (app.name === "Shuffle Tools") {
          if (app.actions !== undefined && (app.actions === null || app.actions.length === 1)) {
            loadAppConfig(app.id, false)
          }
        }
      }, [])

      if (app === undefined || app === null) {
        return (
          <img
            style={{
              pointerEvents: "none",
              userDrag: "none",
              userSelect: "none",
              borderRadius: theme.palette?.borderRadius,
              height: isMobile ? 40 : 55,
              width: isMobile ? 40 : 55,
            }}
          />
        )
      }

      if (app.type !== "TRIGGER" && (app.id === "" || app.name === "")) {
        return null
      }

      const maxlen = 35
      var newAppname = app.name
      newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1)
      if (newAppname.length > maxlen) {
        newAppname = newAppname.slice(0, maxlen) + ".."
      }

      newAppname = newAppname.replaceAll("_", " ")

      if (actionString !== undefined && actionString !== null && actionString.length > 0 && app.actions !== undefined && app.actions !== null && app.actions.length > 0) {
        // Find the action and make it the FIRST one in the list if possible
        const foundAction = app.actions.findIndex((action) => action.name === actionString)
        if (foundAction !== undefined && foundAction !== null && foundAction >= 0) {
          // Move the action to the front
          var action = app.actions[foundAction]

          // Change the Large Image
          const iconInfo = GetIconInfo(action)
          const bgColor = iconInfo.iconBackgroundColor || "#f76b1c";
          var pathElement;
          if (iconInfo.svgViewBox === "0 0 48 48") {
            pathElement = buildIconSvgPath(iconInfo, iconInfo.iconColor);
          } else if (iconInfo.useStroke) {
            pathElement = `<path d="${iconInfo.icon}" fill="none" stroke="${iconInfo.iconColor}" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round" transform="scale(2)"/>`;
          } else {
            pathElement = `<path d="${iconInfo.icon}" fill="${iconInfo.iconColor}" transform="translate(12, 12)"/>`;
          }
          const svg_pin = `<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" rx="8" fill="${bgColor}"/>${pathElement}</svg>`;
          const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
          app.large_image = svgpin_Url
          app.fillGradient = iconInfo.fillGradient
          app.fillstyle = "linear-gradient"
          action.fillstyle = "linear-gradient"
          action.fillGradient = iconInfo.fillGradient
          action.iconBackground = iconInfo.iconBackgroundColor

          // newSelectedAction.iconBackground = iconInfo.iconBackgroundColor;
          app.actions.splice(foundAction, 1)
          app.actions.unshift(action)
        }
      }

      if (app.large_image === undefined || app.large_image === null || app.large_image === "") {
        app.large_image = theme.palette.defaultImage
      }

      const image = app.large_image !== undefined && app.large_image !== null && app.large_image !== "" ? app.large_image : theme.palette.defaultImage

      const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle))
      const pixelSize = !hover ? "2px" : "4px";
      newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0
        ? `${pixelSize} solid ${green}`
        : `${pixelSize} solid ${yellow}`;

      if (app.id == highlightedApp && app.id !== "") {
        //console.log("Found correct appid to highlight: ", app.id)

        newAppStyle.border = "3px solid " + green
      }

      if (!app.activated && app.generated) {
        newAppStyle.borderLeft = `${pixelSize} solid ${yellow}`;
      }

      if (small === true) {
        newAppStyle.width = 55
        newAppStyle.minWidth = newAppStyle.width
        newAppStyle.maxWidth = newAppStyle.width

        // Transparent background on paper
        newAppStyle.border = hover ? "1px solid " + theme?.palette.main : "1px solid transparent"

        // If action is set, look for the icon of it with the normal setup
      }

      newAppStyle.backgroundColor = hover ? theme.palette.parsedAppPaperColor : "transparent"
      
      return (
        <Draggable
          onStart={() => {
            dragRef.current = true;
            newAppStyle.zIndex = 9999

          }}
          onDrag={(e) => {
            newAppStyle.zIndex = 9999
            handleAppDrag(e, app)
          }}
          onStop={(e) => {
            dragRef.current = false;
            newAppStyle.zIndex = "none"
            handleDragStop(e, app)
          }}
          key={app.id}
          dragging={false}
          position={{ x: 0, y: 0 }}
          defaultPosition={{ x: 0, y: 0 }}
        >
          <Paper
            square
            elevation={0}
            style={newAppStyle}
            onMouseEnter={(e) => {
              if (!dragRef.current) {
                e.preventDefault();
                e.stopPropagation();
                setHover(true);

                if (app.actions !== undefined && (app.actions === null || app.actions.length === 1)) {
                  loadAppConfig(app.id, false);
                }
              }
            }}
            onMouseLeave={() => {
              setHover(false);
            }}
            onClick={() => {
              if (isMobile) {
                newNodeId = uuidv4();
                const actionType = "ACTION";
                const actionLabel = getNextActionName(app.name);
                var parameters = null;
                var example = "";
                var description = ""

                if (
                  app.actions[0].parameters !== null &&
                  app.actions[0].parameters.length > 0
                ) {
                  parameters = app.actions[0].parameters;
                }

                if (
                  app.actions[0].returns.example !== undefined &&
                  app.actions[0].returns.example !== null &&
                  app.actions[0].returns.example.length > 0
                ) {
                  example = app.actions[0].returns.example;
                }

                if (
                  app.actions[0].description !== undefined &&
                  app.actions[0].description !== null &&
                  app.actions[0].description.length > 0
                ) {
                  description = app.actions[0].description
                }

                const parsedEnvironments =
                  environments === null || environments === []
                    ? "cloud"
                    : environments[defaultEnvironmentIndex] === undefined
                      ? "cloud"
                      : environments[defaultEnvironmentIndex].Name;

                // activated: app.generated === true ? app.activated === false ? false : true : true,
                const newAppData = {
                  app_name: app.name,
                  app_version: app.app_version,
                  app_id: app.id,
                  sharing: app.sharing,
                  private_id: app.private_id,
                  description: description,
                  environment: parsedEnvironments,
                  errors: [],
                  finished: false,
                  id_: newNodeId,
                  _id_: newNodeId,
                  id: newNodeId,
                  is_valid: true,
                  label: actionLabel,
                  type: actionType,
                  name: app.actions[0].name,
                  parameters: parameters,
                  isStartNode: false,
                  large_image: image,
                  run_magic_output: false,
                  authentication: [],
                  execution_variable: undefined,
                  example: example,
                  category:
                    app.categories !== null &&
                      app.categories !== undefined &&
                      app.categories.length > 0
                      ? app.categories[0]
                      : "",
                  authentication_id: "",
                  finished: false,
                };

                const nodeToBeAdded = {
                  group: "nodes",
                  data: newAppData,
                  renderedPosition: {
                    x: 100,
                    y: 100,
                  },
                };

                parsedApp = nodeToBeAdded;
                cy.add(nodeToBeAdded);

              }
            }}
          >
            <Tooltip title={(actionString?.replaceAll("_", " "))} placement="top" disabled={small !== true}>
              <Grid
                container
                style={{ display: "flex", margin: (small === true || app.type === "TRIGGER") ? 1 : 5, flex: "10", backgroundColor: "transparent" }}
              >
                <Grid item style={{backgroundColor: "transparent", padding: 0}}>
                  <img
                    id={`image_${props?.index}`}
                    src={image}
                    onError={(e) => {
                      if (props.index !== undefined && props.index !== null) {
                        // Replace the image with the default image

                        const foundImage = document.getElementById(`image_${props.index}`)
                        if (foundImage !== undefined && foundImage !== null) {
                          foundImage.src = theme.palette.defaultImage
                          app.large_image = theme.palette.defaultImage
                        }
                      }
                    }}
                    style={{
                      pointerEvents: "none",
                      userDrag: "none",
                      userSelect: "none",
                      borderRadius: theme.palette?.borderRadius,
                      height: isMobile ? 40 : (small === true || app.type === "TRIGGER") ? 52 : 45,
                      width: isMobile ? 40 : (small === true || app.type === "TRIGGER") ? 52 : 45,
                      background: `linear-gradient(to right, ${app.fillGradient})`,
                    }}
                  />
                </Grid>
                {isMobile || small === true ? null :
                  <Grid item
                    style={{
                      display: "flex",
                      flexDirection: "column",
                      marginLeft: "20px",
                      minWidth: 150,
                      maxWidth: 150,
                      overflow: "hidden",
                      maxHeight: 77,
                    }}
                  >
                    <Grid item style={{ flex: 1 }}>
                      <Typography
                        variant="body1"
                        style={{
                          marginBottom: 0,
                          marginLeft: 5,
                          marginTop: newAppname.length > 17 ? -3 : 8,
                          color: theme.palette.textPrimary
                        }}
                      >
                        {newAppname}
                      </Typography>
                    </Grid>
                  </Grid>
                }
              </Grid>
            </Tooltip>
          </Paper>
        </Draggable>
      );
    };

    const runSearch = (value) => {
      value = value.trim().toLowerCase()
      if (value.length > 0) {
        // Dedup based on name
        const preppedApps = Array.prototype.concat.apply(allApps, triggers).filter((app, index, self) => {
          return index === self.findIndex((t) => (
            t.name === app.name
          ))
        })

        var newApps = preppedApps.filter(
          (app) =>
            app.name.toLowerCase().includes(value)
            ||
            app.description.toLowerCase().includes(value)
        )

        // Extend search
        if (newApps.length === 0) {
          newApps = allApps.filter((app) => {
            if (app.actions !== undefined && app.actions !== null) {
              for (let actionkey in app.actions) {
                const inneraction = app.actions[actionkey]
                if (inneraction.name.toLowerCase().includes(value)) {
                  return true;
                }
              }
            }

            return false;
          })
        }

        setVisibleApps(newApps)
      } else {
        setVisibleApps(
          prioritizedApps.concat(
            filteredApps.filter(
              (innerapp) => !internalIds.includes(innerapp.id.toLowerCase())
            )
          )
        )
      }
    };

    const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => {
      const debouncedRefine = useDebouncedCallback(refine, 500)
      const lastRefinedRef = useRef(currentRefinement)

      const safeRefine = (value) => {
        if (value === lastRefinedRef.current) return
        lastRefinedRef.current = value
        debouncedRefine(value)
      }

      if (document !== undefined) {
        const appsearchValue = document.getElementById("appsearch")
        if (appsearchValue !== undefined && appsearchValue !== null) {
          if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) {
            safeRefine(appsearchValue.value)
          }
        }
      }

      return (
        <form id="search_form" noValidate type="searchbox" action="" role="search" style={{ margin: 0, display: "none", }} onClick={() => {
        }}>
          <TextField
            fullWidth
            style={{ backgroundColor: theme.palette.inputColor, borderRadius: theme.palette?.borderRadius, maxWidth: leftBarSize - 20, }}
            InputProps={{
              style: {
              },
              startAdornment: (
                <InputAdornment position="start">
                  <SearchIcon style={{ marginLeft: 5 }} />
                </InputAdornment>
              ),
            }}
            autoComplete='off'
            type="search"
            color="primary"
            placeholder="Find Public Apps, Workflows, Documentation and more"
            value={currentRefinement}
            id="shuffle_search_field"
            onBlur={(event) => {
              //setSearchOpen(false)
            }}
            onChange={(event) => {
              //if (event.currentTarget.value.length > 0 && !searchOpen) {
              //	setSearchOpen(true)
              //}
              safeRefine(event.currentTarget.value)
            }}
            limit={15}
          />
          {/*isSearchStalled ? 'My search is stalled' : ''*/}
        </form>
      )
    }


    const AppHits = ({ hits }) => {
      const [mouseHoverIndex, setMouseHoverIndex] = useState(0)

      //var tmp = searchOpen
      //if (!searchOpen) {
      //	return null
      //}

      const positionInfo = document.activeElement.getBoundingClientRect()
      const outerlistitemStyle = {
        width: "80%",
        overflowX: "hidden",
        overflowY: "hidden",
        borderBottom: "1px solid rgba(255,255,255,0.4)",
      }

      if (hits.length > 4) {
        hits = hits.slice(0, 4)
      }


      const clickedApp = (hit) => {
        toast.success(`Activating App. Please wait a moment, and it will show up highlighted in your apps.`)

        const queryID = hit.__queryID


        if (queryID !== undefined && queryID !== null) {
          aa('init', {
            appId: "JNSS5CFDZZ",
            apiKey: "c8f882473ff42d41158430be09ec2b4e",
          })

          const timestamp = new Date().getTime()
          aa('sendEvents', [
            {
              eventType: 'conversion',
              eventName: 'Public App Activated',
              index: 'appsearch',
              objectIDs: [hit.objectID],
              timestamp: timestamp,
              queryID: queryID,
              userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
            }
          ])
        } else {
          console.log("No query to handle when activating")
        }

        activateApp(hit.objectID, true)
      }

      var type = "app"
      const baseImage = <LibraryBooksIcon />
	  const width = 230
      return (
        <div style={{ position: "relative", marginTop: 15, marginLeft: 0, marginRight: 10, position: "absolute", color: theme.palette.textColor, zIndex: 1001, backgroundColor: theme.palette.textFieldStyle.backgroundColor, /*minWidth: leftBarSize+30,*/ minWidth: width, maxWidth: width, boxShadows: "none", overflowX: "hidden", }}>
          <List style={{ backgroundColor: theme.palette.inputColor, }}>
            {hits.length === 0 ?
              <ListItem style={outerlistitemStyle}>
                <ListItemAvatar onClick={() => console.log(hits)}>
                  <Avatar>
                    <FolderIcon />
                  </Avatar>
                </ListItemAvatar>
                <ListItemText
                  primary={"No public apps found."}
                  secondary={"Try a broader search term"}
                />
              </ListItem>
              :
              hits.map((hit, index) => {
                const innerlistitemStyle = {
                  width: positionInfo.width + 35,
                  overflowX: "hidden",
                  overflowY: "hidden",
                  borderBottom: "1px solid rgba(255,255,255,0.4)",
                  backgroundColor: mouseHoverIndex === index ? theme.palette.hoverColor : "inherit",
                  cursor: "pointer",
                  marginLeft: 0,
                  marginRight: 0,
                  maxHeight: 75,
                  minHeight: 75,
                  maxWidth: 420,
                  minWidth: "100%",
                }

                const name = hit.name === undefined ?
                  hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title :
                  (hit.name.charAt(0).toUpperCase() + hit.name.slice(1)).replaceAll("_", " ")

                var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40) + "..." : ""
                const avatar = hit.image_url === undefined ?
                  baseImage
                  :
                  <Avatar
                    src={hit.image_url}
                    variant="rounded"
                  />

                //console.log(hit)
                if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) {
                  secondaryText = hit.categories.slice(0, 3).map((data, index) => {
                    if (index === 0) {
                      return data
                    }

                    return ", " + data

                    /*
                      <Chip
                        key={index}
                        style={chipStyle}
                        label={data}
                        onClick={() => {
                          //handleChipClick
                        }}
                        variant="outlined"
                        color="primary"
                      />
                    */
                  })
                }

                var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}`
                parsedUrl += `?queryID=${hit.__queryID}`

                var appdragged = false
                return (
                  <Draggable
                    onDrag={(e) => {
                      e.preventDefault()
                      e.stopPropagation()

                      handleAppDrag(e, hit)
                      if (!appdragged) {
                        clickedApp(hit)
                      }

                      appdragged = true
                    }}
                    onStop={(e) => {
                    }}
                    dragging={false}
                    position={{
                      x: 0,
                      y: 0,
                    }}
                    defaultPosition={{ x: 0, y: 0 }}
                  >
                    <div style={{ overflow: "hidden", textDecoration: "none", color: theme.palette.text.primary, }} onClick={(event) => {
                      clickedApp(hit)

                    }}>
                      <ListItem key={hit.objectID} style={innerlistitemStyle} onMouseOver={() => {
                        setMouseHoverIndex(index)
                      }}>
                        <ListItemAvatar>
                          {avatar}
                        </ListItemAvatar>
                        <ListItemText
                          primary={name}
                          secondary={secondaryText}
                        />
                        {/*
												<ListItemSecondaryAction>
													<IconButton edge="end" aria-label="delete">
														<DeleteIcon />
													</IconButton>
												</ListItemSecondaryAction>
												*/}
                      </ListItem>
                    </div>
                  </Draggable>
                )
              })
            }
          </List>
        </div>
      )
    }


    const CustomSearchBox = connectSearchBox(SearchBox)
    const CustomAppHits = connectHits(AppHits)

    var shuffleToolsApp = apps.find((app) => app.name === "Shuffle Tools")
    if (shuffleToolsApp !== undefined && shuffleToolsApp !== null) {
      shuffleToolsApp = JSON.parse(JSON.stringify(shuffleToolsApp))
    }

    var viewedApps = []

    const QuickAccessSection = ({ title, items, renderItem }) => (
      <div style={{ marginBottom: 25 }}>
        <Typography
          variant="body1"
          style={{ marginTop: 20, marginLeft: 5, color: theme.palette.text.primary, fontFamily: theme?.typography?.fontFamily }}
        >
          {title}
        </Typography>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
          {items.map((item, index) => renderItem(item, index))}
        </div>
      </div>
    );

    // Popular Shuffle Tools actions
    const popularActions = [
      ["repeat_back_to_me", "execute_python", "get_file_value", "set_cache_value"],
      ["get_file_meta", "send_sms_shuffle", "filter_list", "merge_lists"],
    ];
    return (
      <div style={appViewStyle}>
        <div style={{ flex: "1", zoom: 0.9, }}>
          <TextField
            style={{
              backgroundColor: theme.palette.usecaseCardColor,
              borderRadius: theme.palette?.borderRadius,
              marginTop: 10,
              marginRight: 10,
              height: 40,
            }}
            InputProps={{
              style: {
                height: 40,
                borderRadius: theme.palette?.borderRadius,
                backgroundColor: theme.palette.usecaseCardColor,
              },
              startAdornment: (
                <InputAdornment position="start">
                  <Tooltip title="Run search" placement="top">
                    <SearchIcon style={{ cursor: "pointer" }} />
                  </Tooltip>
                </InputAdornment>
              ),
            }}
            fullWidth
            color="primary"
            placeholder={"Search apps, triggers..."}
            id="appsearch"
            onKeyPress={(event) => {
              if (event.key === "Enter") {
                event.target.blur(event);
              }
            }}
            onChange={(event) => {
              runSearch(event.target.value)
            }}
            onBlur={(event) => {
              //navigate(`?q=${event.target.value}`)

              //runSearch(event.target.value)
            }}
          />


          {shuffleToolsApp && !document?.getElementById("appsearch")?.value?.length && shuffleToolsApp?.actions?.length > 1 && (
            <QuickAccessSection
              title="Popular Actions"
              items={popularActions.flat()}
              renderItem={(action) => (
                <ParsedAppPaper
                  key={action}
                  small={true}
                  action={action}
                  app={shuffleToolsApp}
                  skip_load={true}
                />
              )}
            />
          )}

          {!document?.getElementById("appsearch")?.value?.length && (
            <QuickAccessSection
              title="Triggers"
              items={triggers.filter(t => t.trigger_type !== "PIPELINE")}
              renderItem={(trigger, index) => (
                <ParsedAppPaper
                  key={index}
                  small={true}
                  action={trigger.name}
                  app={JSON.parse(JSON.stringify(trigger))}
                />
              )}
            />
          )}

          {visibleApps.length > extraApps.length ? 
			  <Typography variant="body1" style={{ marginTop: 20, marginLeft: 5, color: theme.palette.text.primary, fontFamily: theme?.typography?.fontFamily }}>
				Your Apps
			  </Typography>
		  : null}

          {visibleApps.length > extraApps.length ? (
            <div style={appScrollStyle}>
              {visibleApps.map((app, index) => {
                if (app.invalid) {
                  return null
                }

                if (app.trigger_type === "PIPELINE" && userdata.support !== true) {
                  	return null
                }

                //if ((app.id === "shuffle_agent") && userdata.support !== true) {
                //  	return null
				//}

                if (viewedApps.includes(app.id)) {
                  	return null
                }

                if (app.trigger_type !== undefined && app.trigger_type !== null && app.trigger_type.length > 0) {
                  app.is_valid = true
                  app.actions = [{ "name": "empty" }]
                }

                viewedApps.push(app.id)

                var extraMessage = ""
                if (index == 2) {
                  extraMessage = <div style={{ marginTop: 5 }} />
                }

                delay += 75
                return (
                  runDelay ?
                    <span>
                      {/*<Zoom key={index} in={true} style={{ transitionDelay: `${delay}ms` }}>*/}
                      <div>
                        <ParsedAppPaper key={index} index={index} app={app} />
                      </div>
                      {/*</Zoom>*/}
                    </span>
                    :
                    <div key={index}>
                      {extraMessage}
                      <ParsedAppPaper key={index} index={index} app={app} />
                    </div>
                )
              })}

              {visibleApps.length <= 2 ?
                <div
                  style={{ textAlign: "center", width: leftBarSize, marginTop: 40, maxWidth: 340, overflow: "hidden", }}
                  onLoad={() => {
                  }}
                >
                  <Typography variant="body1" color="textSecondary">
                    Click one of the public apps below to Activate it for your organization.
                  </Typography>
                  <InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
                    console.log("CLICKED")
                  }}>
                    <CustomSearchBox />
                    <Index indexName="appsearch">
                      <CustomAppHits />
                    </Index>
                  </InstantSearch>
                </div>
                :
                <div style={{ marginLeft: 10, marginTop: 15, marginBottom: 100, }}>
                  <Typography variant="body1" color="textSecondary">
                    <b>Apps need to be activated</b> before they can be used. Search in the search bar from our 2500+ apps to activate them for.
                  </Typography>
                </div>
              }
            </div>
          ) : apps.length > 0 ? (
            <div
              style={{ textAlign: "center", width: leftBarSize, marginTop: 25, marginLeft: 10 , marginRight: 10, }}
              onLoad={() => {
                console.log("Should load in extra apps?")
              }}
            >
              <Typography variant="body1" color="textSecondary">
                Couldn't find the apps you were looking for? Searching unactivated apps. Click one of these apps to Activate it for your organisation.
              </Typography>

              <InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
                console.log("CLICKED")
              }}>
                <CustomSearchBox />
                <Index indexName="appsearch">
                  <CustomAppHits />
                </Index>
              </InstantSearch>
            </div>
          ) : (
            <div style={{ textAlign: "center", width: leftBarSize }}>
              <CircularProgress
                style={{
                  marginTop: "27vh",
                  height: 35,
                  width: 35,
                  marginLeft: "auto",
                  marginRight: "auto",
                }}
              />
              <Typography variant="body1" color="textSecondary">
                Loading Apps
              </Typography>
            </div>
          )}
        </div>
      </div>
    );
  }



  const getNextActionName = (appName) => {
    var highest = "";

    const allitems = workflow.actions.concat(workflow.triggers);
    if (allitems !== undefined && allitems !== null) {
      for (let itemkey in allitems) {
        const item = allitems[itemkey];

        if (item.app_name === appName && item.label !== undefined && item.label !== null) {
          var number = item.label.split("_");
          if (isNaN(number[-1]) && parseInt(number[number.length - 1]) > highest) {
            highest = number[number.length - 1];
          }
        }
      }
    }

    appName = appName.replaceAll(" ", "_")
    if (highest) {
      return appName + "_" + (parseInt(highest) + 1);
    } else {
      return appName + "_" + 1;
    }
  };

  const setNewSelectedAction = (e) => {
    if (selectedApp.actions === undefined || selectedApp.actions === null) {
      return
    }

    if (selectedApp.actions.length === 1) {
      // Find if there's a new app
      const newApp = apps.find((app) => (app.name === selectedApp.name && app.app_version !== selectedApp.app_version) || app.id == selectedApp.id)
      if (newApp !== undefined && newApp !== null) {

        if (selectedApp.actions !== undefined && selectedApp.actions !== null && selectedApp.actions.length > 1) {
          setSelectedApp(newApp)
        }

        selectedApp.actions = newApp.actions
      }
    }

    const newaction = selectedApp.actions.find((a) => a.name === e.target.value)
    if (newaction === undefined || newaction === null) {
      toast(`Failed to find the action you selected. Please try again or contact ${supportEmail} if it persists.`);
      return;
    }

    if (workflow.actions !== undefined && workflow.actions !== null) {
      const foundInfo = workflow.actions.find(ac => ac.id === selectedAction.id)
    }


    // Setting an old reference just to use the same memory space elsewhere 
    // for selectedAction
    const oldaction = JSON.parse(JSON.stringify(selectedAction))

    // Does this one find the wrong one?
    //var newSelectedAction = JSON.parse(JSON.stringify(selectedAction))
    var newSelectedAction = selectedAction
    newSelectedAction.name = newaction.name;
    newSelectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
    newSelectedAction.errors = [];
    newSelectedAction.isValid = true;
    newSelectedAction.is_valid = true;
    newSelectedAction.required_body_fields = newaction.required_body_fields

    // Simple action swap autocompleter
    if (oldaction.parameters !== undefined && oldaction.parameters !== null && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) {
      var fileid_found = false

      for (let [paramkey, paramkeyval] in Object.entries(oldaction.parameters)) {
        const param = oldaction.parameters[paramkey];

        if (param.name === "file_id") {
          fileid_found = true
        }

        if (param.value === null || param.value === undefined || param.value.length === 0) {
          continue
        }

        if (param.name === "body") {
          //console.log("Param: ", param)
          continue
        }

        if (param.name === "headers") {
          if (fileid_found) {
            newSelectedAction.parameters[paramkey].value = ""
            newSelectedAction.parameters[paramkey].autocompleted = true

            continue
          }
        }

        if (newSelectedAction.parameters === undefined || newSelectedAction.parameters === null) {
          continue
        }

        // Not doing options fields
        const newParamIndex = newSelectedAction.parameters.findIndex(paramdata => paramdata.name === param.name)
        if (newParamIndex < 0) {
          continue
        }

        if (newSelectedAction.parameters[newParamIndex].name === "headers") {
          if (param.value !== undefined && param.value !== null && param.value.includes("=undefined")) {
            if (newSelectedAction.parameters[newParamIndex].example !== undefined && newSelectedAction.parameters[newParamIndex].example !== null) {
              newSelectedAction.parameters[newParamIndex].value = newSelectedAction.parameters[newParamIndex].example
            } else {
              newSelectedAction.parameters[newParamIndex].value = ""
            }

            continue
          }
        }

        newSelectedAction.parameters[newParamIndex].value = param.value
        newSelectedAction.parameters[newParamIndex].autocompleted = true
        if (param.options !== undefined && param.options !== null && param.options.length > 0) {
          newSelectedAction.parameters[newParamIndex].autocompleted = false
        }
      }
    }

    if (newSelectedAction.app_name === "Shuffle Tools") {
      const iconInfo = GetIconInfo(newSelectedAction);
      const iconViewBox = iconInfo.svgViewBox || `0 0 ${svgSize} ${svgSize}`;
      const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="${iconViewBox}" version="1.1" xmlns="http://www.w3.org/2000/svg">${buildIconSvgPath(iconInfo, "white")}</svg>`;
      const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
      newSelectedAction.large_image = svgpin_Url;
      newSelectedAction.fillGradient = iconInfo.fillGradient;
      newSelectedAction.fillstyle = "solid";
      if (newSelectedAction.fillGradient !== undefined && newSelectedAction.fillGradient !== null && newSelectedAction.fillGradient.length > 0) {
        newSelectedAction.fillstyle = "linear-gradient";
      } else {
        newSelectedAction.iconBackground = iconInfo.iconBackgroundColor;
      }

      const foundnode = cy.getElementById(newSelectedAction.id);
      if (foundnode !== null && foundnode !== undefined) {
        foundnode.data(newSelectedAction);
      }
    }

    for (var preparamindex in newSelectedAction.parameters) {
      const param = newSelectedAction.parameters[preparamindex]
      if (param.configuration === true) {
        continue
      }

      if (param?.value?.toLowerCase().includes("secret. replace")) {
        newSelectedAction.parameters[preparamindex].value = ""
      }
    }

    // Takes an action as input, then runs through and updates the relevant parameters based on previous actions' results (parent nodes)
    // Further checks if those fields are already set in a previously used action
    newSelectedAction = RunAutocompleter(newSelectedAction);

    if (
      newaction.return !== undefined &&
      newaction.return !== null &&
      newaction.returns.example !== undefined &&
      newaction.returns.example !== null &&
      newaction.returns.example.length > 0
    ) {
      newSelectedAction.example = newaction.returns.example;
    }


    if (
      newaction.description !== undefined &&
      newaction.description !== null &&
      newaction.description.length > 0
    ) {
      newSelectedAction.description = newaction.description
    }

    // FIXME - this is broken sometimes lol
    //var env = environments.find(a => a.Name === newaction.environment)
    //if ((!env || env === undefined) && selectedAction.environment === undefined ) {
    //	env = environments[defaultEnvironmentIndex]
    //}
    //setSelectedActionEnvironment(env)


    if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
      const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id)
      if (foundActionIndex >= 0) {
        workflow.actions[foundActionIndex] = newSelectedAction
        setWorkflow(workflow)
      }
    }

    // Last fix for params
    if (newSelectedAction.parameters !== undefined && newSelectedAction.parameters !== null && newSelectedAction.parameters.length > 0) {
      for (let paramkey in newSelectedAction.parameters) {
        const param = newSelectedAction.parameters[paramkey]
        if (param.name !== "body") {
          continue
        }

        if (param.example !== undefined && param.example !== null && param.example.length > 0) {
          if (param.value === undefined || param.value === null || param.value.length === 0) {
            param.value = param.example
          }
        }

        newSelectedAction.parameters[paramkey] = param
      }
    }

    setSelectedAction(newSelectedAction)
    setUpdate(Math.random())

    const allNodes = cy.nodes().jsons()
    if (allNodes !== undefined && allNodes !== null) {
      for (let nodekey in allNodes) {
        const currentNode = allNodes[nodekey];
        if (
          currentNode.data.attachedTo === oldaction.id &&
          currentNode.data.isDescriptor
        ) {
          const foundnode = cy.getElementById(currentNode.data.id);
          if (foundnode !== null && foundnode !== undefined) {
            const iconInfo = GetIconInfo(newaction);
            const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
            const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
            foundnode.data("image", svgpin_Url);
            foundnode.data("imageColor", iconInfo.iconBackgroundColor);
          }

          break;
        }
      }
    }

    // Send it in here, after all fields are filled
    // Disabled for now :(
    // const aiMsg = "Fill based on previous values"
    // aiSubmit(aiMsg, undefined, undefined, newSelectedAction)
  };

  // APPSELECT at top
  // appname & version
  // description
  // ACTION select
  const selectedNameChange = (appActionName) => {
    if (appActionName === undefined || appActionName === null) {
      return
    }

    appActionName = appActionName.replaceAll("(", "");
    appActionName = appActionName.replaceAll(")", "");
    appActionName = appActionName.replaceAll("]", "");
    appActionName = appActionName.replaceAll("[", "");
    appActionName = appActionName.replaceAll("{", "");
    appActionName = appActionName.replaceAll("}", "");
    appActionName = appActionName.replaceAll("*", "");
    appActionName = appActionName.replaceAll("!", "");
    appActionName = appActionName.replaceAll("@", "");
    appActionName = appActionName.replaceAll("#", "");
    appActionName = appActionName.replaceAll("$", "");
    appActionName = appActionName.replaceAll("%", "");
    appActionName = appActionName.replaceAll("&", "");
    appActionName = appActionName.replaceAll("#", "");
    appActionName = appActionName.replaceAll(".", "");
    appActionName = appActionName.replaceAll(",", "");
    appActionName = appActionName.replaceAll(" ", "_");
    appActionName = appActionName.replaceAll("^", "_");
    appActionName = appActionName.replaceAll("'", "_");
    appActionName = appActionName.replaceAll("\"", "_");
    appActionName = appActionName.replaceAll("\"", "_");
    appActionName = appActionName.replaceAll(":", "_");
    appActionName = appActionName.replaceAll(";", "_");
    appActionName = appActionName.replaceAll("=", "_");
    appActionName = appActionName.replaceAll("+", "_");
    selectedAction.label = appActionName;
    setSelectedAction(selectedAction);
  };

  const actionDelayChange = (delay) => {
    if (isNaN(delay)) {
      console.log("NAN: ", delay)
      return
    }

    const parsedNumber = parseInt(delay)
    if (parsedNumber > 86400) {
      console.log("Max number is 1 day (86400)")
      return
    }

    selectedAction.execution_delay = parsedNumber
    setSelectedAction(selectedAction)
  }

  const selectedTriggerChange = (event) => {
    selectedTrigger.label = event.target.value;
    setSelectedTrigger(selectedTrigger);
    workflow.triggers[selectedTriggerIndex].label = event.target.value;
    setWorkflow(workflow);
  };

  // Starts on current node and climbs UP the tree to the root object.
  // Sends back everything in it's path
  // FIXME: Use the GetParentNodes in WorkflowValidationTimeline.jsx instead
  const getParents = (action) => {
    if (action === undefined || action === null) {
      return []
    }

    var allkeys = [action.id];
    var handled = [];
    var results = [];

    if (cy === undefined || cy === null) {
      return []
    }

    // maxiter = max amount of parent nodes to loop
    // also handles breaks if there are issues
    var iterations = 0;
    var maxiter = 100;
    while (true) {
      for (let parentkey in allkeys) {
        var currentnode = cy.getElementById(allkeys[parentkey]);
        if (currentnode === undefined || currentnode === null) {
          continue;
        }

        if (currentnode.data() === undefined) {
          handled.push(allkeys[parentkey]);
          results.push({ id: allkeys[parentkey], type: "TRIGGER" });
        } else {
          if (handled.includes(currentnode.data().id)) {
            continue
          } else {
            handled.push(currentnode.data().id);
            results.push(currentnode.data());
          }
        }

        // Get the name / label here too?
        if (currentnode.length === 0) {
          continue;
        }

        const incomingEdges = currentnode.incomers("edge");
        if (incomingEdges.length === 0) {
          continue;
        }

        for (let i = 0; i < incomingEdges.length; i++) {
          var tmp = incomingEdges[i];
          if (tmp.data("decorator")) {
            continue
          }

          if (!allkeys.includes(tmp.data("source"))) {
            allkeys.push(tmp.data("source"));
          }
        }
      }

      if (results.length === allkeys.length || iterations === maxiter) {
        break;
      }

      iterations += 1;
    }


    // Remove on the end as we don't want to remove everything
    results = results.filter((data) => data.id !== action.id)
    results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input")
    results.push({ label: "Runtime Argument", type: "INTERNAL" })

    return results
  }

  // BOLD name: type: required?
  // FORM
  // Dropdown -> static, action, local env, global env
  // VALUE (JSON)
  // {data.name}, {data.description}, {data.required}, {data.schema.type}

  //height: "100%",
  const appApiViewStyle = {
    display: "flex",
    flexDirection: "column",
    backgroundColor: theme.palette.DialogStyle.backgroundColor,
    color: theme.palette.text.primary,
    paddingRight: 15,
    paddingLeft: 15,
    minHeight: "100%",
    zIndex: 1000,
    resize: "vertical",
    overflow: "auto",
    paddingBottom: 100,

    overflowAnchor: "none",
  };

  const minSize = 370
  var rightsidebarStyle = {
    position: "fixed",
    top: workflow?.public && !isLoggedIn ? 100 : showWorkflowRevisions ? 100 : appBarSize - 35,
    right: 25,
    height: "90vh",
    maxWidth: 600,
    maxHeight: "100vh",
    border: "1px solid rgb(91, 96, 100)",
    zIndex: 1000,
    borderRadius: theme.palette?.borderRadius,
    resize: "both",
    overflow: "auto",

    overflowAnchor: "none",
    minWidth: minSize,
    width: isMobile ? "100%" : minSize,
    zoom: isSafari ? undefined : 0.9,
    transform: isSafari ? "scale(0.9)" : undefined,
    transformOrigin: isSafari ? "top right" : undefined,
  };

  const setTriggerFolderWrapperMulti = (event) => {
    const { options } = event.target;
    var value = [];
    for (let i = 0, l = options.length; i < l; i += 1) {
      if (options[i].selected) {
        value.push(options[i].value);
      }
    }

    if (selectedTrigger.parameters === null) {
      selectedTrigger.parameters = [[]];
      workflow.triggers[selectedTriggerIndex].parameters = [[]];
    }

    // Max 1 folder for office for some reason. MailFolders('MAILBOX_ID') in resource 
    // Can't parse URL with multiple folders.
    if (selectedTrigger.name === "Office365" & value !== undefined && value !== null && value.length > 1) {
      toast("Max 1 folder at a time allowed for Office365")
      console.log("VALUE: ", value)
      value = [value[0]]
    }

    // This is a dirty workaround for the static values in the go backend and datastore db
    const fixedValue = value.join(splitter);
    selectedTrigger.parameters[0] = {
      value: fixedValue,
      name: "outlookfolder",
    };
    workflow.triggers[selectedTriggerIndex].parameters[0] = {
      value: fixedValue,
      name: "outlookfolder",
    };

    // This resets state for some reason (:
    setSelectedAction({});
    setSelectedTrigger({});
    setSelectedApp({});
    setSelectedEdge({});

    // Set value
    setSelectedTrigger(selectedTrigger);
    setWorkflow(workflow);
  };

  const setTriggerCronWrapper = (value) => {
    if (selectedTrigger.parameters === null) {
      selectedTrigger.parameters = [];
    }
    selectedTrigger.parameters[0] = {
      value: value,
      name: "cron",
    };
    workflow.triggers[selectedTriggerIndex].parameters[0] = {
      value: value,
      name: "cron",
    };
    setWorkflow(workflow);
    setSelectedTrigger(selectedTrigger);
  };

  const setTriggerOptionsWrapper = (value) => {
    if (selectedTrigger.parameters === null || selectedTrigger.parameters === undefined) {
      selectedTrigger.parameters = [
        { name: "", value: "" },
        { name: "", value: "" },
        { name: "", value: "" },
      ]
    }

    if (selectedTrigger.parameters.length < 3) {
      selectedTrigger.parameters.push({ name: "", value: "" })
    }

    const splitItems = workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",");

    console.log(splitItems);
    if (splitItems.includes(value)) {
      for (let i = 0; i < splitItems.length; i++) {
        if (splitItems[i] === value) {
          splitItems.splice(i, 1);
        }
      }
    } else {
      splitItems.push(value);
    }

    for (let i = 0; i < splitItems.length; i++) {
      if (splitItems[i] === "") {
        splitItems.splice(i, 1);
      }
    }

    workflow.triggers[selectedTriggerIndex].parameters[2].value = splitItems.join(",");

    setWorkflow(workflow);
    setLocalFirstrequest(!localFirstrequest);
    setUpdate(Math.random());
  };

  const setTriggerTextInformationWrapper = (value) => {
    if (selectedTrigger.parameters === null) {
      selectedTrigger.parameters = [];
    }
    selectedTrigger.parameters[0] = {
      value : value
    }
    workflow.triggers[selectedTriggerIndex].parameters[0] = {
      value: value,
      name: "alertinfo",
    };
    setSelectedTrigger(selectedTrigger)
    setWorkflow(workflow);
  };

  const setTriggerBodyWrapper = (value) => {
    if (selectedTrigger.parameters === null) {
      selectedTrigger.parameters = [];
      workflow.triggers[selectedTriggerIndex].parameters[0] = {
        value: value,
        name: "cron",
      }
    }

    workflow.triggers[selectedTriggerIndex].parameters[1] = {
      value: value,
      name: "execution_argument",
    };
    setWorkflow(workflow);
  };

  const AppConditionHandler = (props) => {
    const { tmpdata, type, setExpansionModalOpen, setActiveDialog, setEditorData, } = props;
    const [data] = useState(tmpdata);
    const [multiline, setMultiline] = useState(false);
    const [showDropdown, setShowDropdown] = React.useState(false);
    const [actionlist, setActionlist] = React.useState([]);
    const [menuPosition, setMenuPosition] = React.useState(null);

    if (tmpdata === undefined) {
      return tmpdata;
    }

    if (data.variant === "") {
      data.variant = "STATIC_VALUE";
    }

    // Set actions based on NEXT node, since it should be able to involve those two
    if (actionlist.length === 0) {
      // Base execution variables that should always be available
      if (workflowExecutions.length > 0) {
        for (let execution of workflowExecutions) {
          const execArg = execution.execution_argument;
          if (execArg && execArg.length > 0) {
            const valid = validateJson(execArg);
            if (valid.valid) {
              actionlist.push({
                type: "Runtime Argument",
                name: "Runtime Argument",
                value: "$exec",
                highlight: "exec",
                autocomplete: "exec",
                example: valid.result,
              })
  
              break
            }
          }
        }
      }
  
      // Add default Runtime Argument if none were added
      if (actionlist.length === 0) {
        actionlist.push({
          type: "Runtime Argument",
          name: "Runtime Argument",
          value: "$exec",
          highlight: "exec",
          autocomplete: "exec",
          example: "",
        })
      }
      // Add Shuffle DB with cache keys if available
      let cacheKey = {
        type: "Shuffle DB",
        name: "Shuffle Datastore", 
        value: "$shuffle_cache",
        highlight: "shuffle_cache",
        autocomplete: "shuffle_cache",
        example: "",
      }

      if (listCache?.keys?.length > 0) {
        cacheKey.example = {};
        for (let item of listCache.keys) {
          if (item.key) {
            let itemValue = item.value ?? "";
            if (itemValue.length > 10000) {
              itemValue = "";
            }
            cacheKey.example[item.key.split(" ").join("_")] = { value: itemValue };
          }
        }
      }

      actionlist.push(cacheKey);

      // Add workflow variables
      if (workflow.workflow_variables?.length > 0) {
        workflow.workflow_variables.forEach(item => {
          actionlist.push({
            type: "workflow_variable",
            name: item.name,
            value: item.value,
            id: item.id,
            autocomplete: `${item.name.split(" ").join("_")}`,
            example: item.value,
          })
        })
      }

      // Add execution variables with examples from workflow executions
      if (workflow.execution_variables?.length > 0) {
        workflow.execution_variables.forEach(item => {
          let exampleOutput = "";
          if (workflowExecutions?.length > 0) {
            for (let exec of workflowExecutions) {
              const foundExec = exec.execution_variables?.find(exvar => exvar.name === item.name);
              if (foundExec?.value) {
                exampleOutput = foundExec.value;
                break;
              }
            }
          }

          actionlist.push({
            type: "execution_variable",
            name: item.name,
            value: item.value,
            id: item.id,
            autocomplete: `${item.name.split(" ").join("_")}`,
            example: exampleOutput,
          })
        })
      }

      // Get parent nodes and their data
      if (selectedEdge) {
        const nodeId = selectedEdge.target; 

        const node = cy.getElementById(nodeId);
        if (node?.length > 0) {
          const parents = getParents(node.data())
          
          // Add parent actions to the list
          parents.forEach(item => {
            if (item.label === "Runtime Argument") {
              return
            }

            // Get example data from workflow executions if available
            let exampleData = item.example ?? "";
            if (!exampleData && workflowExecutions?.length > 0) {
              for (let exec of workflowExecutions) {
                const foundResult = exec.results?.find(result => result.action.id === item.id);
                if (foundResult) {
                  const valid = validateJson(foundResult.result);
                  if (valid.valid && valid.result.success !== false) {
                    exampleData = valid.result;
                    break;
                  }
                }
              }
            }

            // Add action with its outputs
            actionlist.push({
              type: "action",
              id: item.id,
              name: item.label || "",
              autocomplete: `${item?.label?.split(" ")?.join("_")}`,
              example: exampleData,
            })

            // If the parent is a subflow, add its outputs
            if (item.app_name === "Shuffle Workflow") {
              const subflowOutputs = item.parameters?.find(param => param.name === "workflow_variables")?.value
              if (subflowOutputs) {
                try {
                  const outputs = JSON.parse(subflowOutputs)
                  outputs.forEach(output => {
                    actionlist.push({
                      type: "subflow_variable",
                      id: `${item.id}_${output.name}`,
                      name: `${item.label} - ${output.name}`,
                      value: output.value,
                      autocomplete: `${item.label.split(" ").join("_")}.${output.name}`,
                      example: output.value,
                    })
                  })
                } catch (e) {
                  console.log("Failed to parse subflow outputs:", e)
                }
              }
            }
          })
        }
      }

      setActionlist(actionlist);
    }

    if (
      data.multiline !== undefined &&
      data.multiline !== null &&
      data.multiline === true
    ) {
      setMultiline(true);
    }

    var placeholder = "Static value";
    if (
      data.example !== undefined &&
      data.example !== null &&
      data.example.length > 0
    ) {
      placeholder = data.example;
    }

    var datafield = (
      <div style={{ display: "flex", flexDirection: "column" }}>
        <div style={{ 
          display: "flex", 
          alignItems: "center",
          gap: 10,
          padding: "5px 0",
          position: "relative"
        }}>
          <TextField
            style={{
              backgroundColor: theme.palette.inputColor,
              borderRadius: theme.palette?.borderRadius,
              flex: 1,
            }}
            InputProps={{
              style: {
                minHeight: 45,
                fontSize: "1rem",
                padding: "0 12px",
              },
              endAdornment: (
                <InputAdornment position="end">
                  <IconButton
                    size="small"
                    style={{
                      backgroundColor: theme.palette.inputColor,
                      width: 35,
                      height: 35,
                      padding: 0,
                    }}
                    onClick={(event) => {
                      event.preventDefault()
                      setExpansionModalOpen(true)
                      setActiveDialog("codeeditor")
                      navigate(`?condition_id=${data.id}&condition_field=${data.name}`)
                      setEditorData({
                        "name": data.name,
                        "value": data.value || "",
                        "actionlist": actionlist,
                        "example": data.example || "",
                      })
                    }}
                  >
                    <Tooltip title="Expand editor" placement="top">
                      <OpenInFullIcon style={{ 
                        color: theme.palette.textColor,
                        fontSize: 20,
                      }}/>
                    </Tooltip>
                  </IconButton>
                </InputAdornment>
              )
            }}
            fullWidth
            multiline={multiline}
            color="primary"
            defaultValue={data.value}
            placeholder={placeholder}
            helperText={
              data.value !== undefined &&
              data.value !== null &&
              data.value.includes(".#") ? (
                <span style={{ color: theme.palette.text.primary, marginBottom: 5 }}>
                  Use "Shuffle Tools" app with "Filter List" action to handle loops
                </span>
              ) : null
            }
            onBlur={(e) => {
              changeActionVariable(data.action_field, e.target.value);
              setUpdate(Math.random());
            }}
          />
        </div>
      </div>
    );

    const changeActionVariable = (variable, value) => {
      // set the name
      data.value = value;
      data.action_field = variable;

      if (type === "source") {
        setSourceValue(data);
      } else if (type === "destination") {
        setDestinationValue(data);
      }
    };


    // Add the missing handler functions
    const handleMenuClose = () => {
      setMenuPosition(null);
          setShowDropdown(false);
        };


        const handleItemClick = (items) => {
          if (items === undefined || items === null || items.length === 0) {
            return;
          }
        
          // Build the autocomplete string
          let toComplete = sourceValue?.value?.trim()?.endsWith("$") ? 
            items[0].autocomplete : 
            "$" + items[0].autocomplete;
        
          toComplete = toComplete.toLowerCase().replaceAll(" ", "_");
          
          // Add any nested paths
          for (let key in items) {
            if (key == 0 || items[key].autocomplete?.length === 0) {
              continue; 
            }
        
            toComplete += items[key].autocomplete;
          }
        
          // Update the field value based on type
          if (type === "source") {
            handleConditionFieldChange("source", toComplete);
          } else if (type === "destination") {
            handleConditionFieldChange("destination", toComplete);
          }
        
          handleMenuClose();
        };

        const sourceAction = cy.getElementById(selectedEdge.source)
        const targetAction = cy.getElementById(selectedEdge.target)
        const sourceImage = sourceAction?.data()?.large_image
        const targetImage = targetAction?.data()?.large_image
        
    return (
      <div>
        <div
          style={{ marginTop: "20px", marginBottom: "7px", display: "flex", alignItems: "center", marginLeft: "5px" }}
        >

          {
            selectedEdge && Object.keys(selectedEdge).length > 0 ? 
            <img
            src={
              data.name === "source" ?
              sourceImage || "" : 
              targetImage || "" 
            }
            style={{
              width: "30px",
              height: "30px",
              borderRadius: "50%",
              marginRight: 8
            }}
          /> : 
          <div style={{
            width: "17px",
            height: "17px",
            borderRadius: 17 / 2,
            backgroundColor: "#FF8544",
            marginRight: "10px",
          }}
          />
          }
         
          <div style={{ flex: "10" }}>
            <b>{data.name} </b>
          </div>
        </div>
        {datafield}
        {actionlist.length === 0 ? null : (
          <FormControl fullWidth>
            <div
            style={{
              display: "flex",
              justifyContent: "space-between",
              border: "1px solid rgba(255,255,255,0.3)",
              borderRadius: 4,
              paddingRight: "5px",
              alignItems: "center",
              cursor: "pointer",
              marginTop: '3px',
                // Add hover transition
              transition: "all 0.2s ease-in-out",
              // Add hover background color
              '&:hover': {
                backgroundColor: "rgba(255,255,255,0.1)",
                border: "1px solid rgba(255,255,255,0.5)",
              }
            }}
             onClick={(event) => {
              // Prevent the event from bubbling up
              event.preventDefault();
              event.stopPropagation();
              
              const rect = event.currentTarget.getBoundingClientRect();
              // Ensure we have valid numbers for positioning 
              const newPosition = {
                top: rect.bottom + window.scrollY,
                left: rect.left + window.scrollX,
              };
              
              // Only set state if we have valid coordinates
              if (typeof newPosition.top === 'number' && typeof newPosition.left === 'number') {
                setMenuPosition(newPosition);
                setShowDropdown(true);
              }
            }}
            >
            <Button
            variant="outlined"
            size="small"
            startIcon={<AddCircleOutlineIcon/>}
            sx={{ 
              marginLeft: 10,
              color: theme.palette.text.primary,
              fontSize: "15px",
              fontFamily: theme?.typography?.fontFamily,
              textTransform: "none",
              padding: "10px 10px",
              border: "none",
              width: "100%",
              marginLeft: "0px",
              paddingLeft: "20px",
              justifyContent: "flex-start",
              "&:hover": {
                backgroundColor: "transparent",
                color: theme.palette.text.primary,
                border: "none"
              },
              // Disable ripple effect
              "& .MuiTouchRipple-root": {
                display: "none"
              }
            }}
            >
              <Tooltip title="Show available variables" placement="top">
                Auto Complete
              </Tooltip>
            </Button>
            <KeyboardArrowDownIcon
            style={{

            }}
            />
            </div>
            {showDropdown && menuPosition && menuPosition.top && menuPosition.left && (
            <Menu
              id="action-menu"
              open={true}
              anchorReference="anchorPosition"
              anchorPosition={menuPosition}
              onClose={handleMenuClose}
              PaperProps={{
                style: {
                  backgroundColor: theme.palette.backgroundColor,
                  color: theme.palette.text.primary,
                  marginTop: 2,
                  maxHeight: 400,
                },
              }}
            >
              {actionlist.map((innerdata) => {
                const icon =
                  innerdata.type === "action" ? (
                    <AppsIcon style={{ marginRight: 10 }} />
                  ) : innerdata.type === "workflow_variable" ||
                    innerdata.type === "execution_variable" ? (
                    <FavoriteBorderIcon style={{ marginRight: 10 }} />
                  ) : (
                    <ScheduleIcon style={{ marginRight: 10 }} />
                  );

                const handleExecArgumentHover = (inside) => {
                  var exec_text_field = document.getElementById(
                    "execution_argument_input_field"
                  );
                  if (exec_text_field !== null) {
                    if (inside) {
                      exec_text_field.style.border = "2px solid #FF8544";
                    } else {
                      exec_text_field.style.border = "";
                    }
                  }

                  // Also doing arguments
                  if (
                    workflow.triggers !== undefined &&
                    workflow.triggers !== null &&
                    workflow.triggers.length > 0
                  ) {
                    for (let triggerkey in workflow.triggers) {
                      const item = workflow.triggers[triggerkey];

                      if (cy !== undefined) {
                        var node = cy.getElementById(item.id);
                        if (node.length > 0) {
                          if (inside) {
                            node.addClass("shuffle-hover-highlight");
                          } else {
                            node.removeClass("shuffle-hover-highlight");
                          }
                        }
                      }
                    }
                  }
                }

                const handleActionHover = (inside, actionId) => {
                  if (cy !== undefined && cy !== null) {
                    var node = cy.getElementById(actionId);
                    if (node.length > 0) {
                      if (inside) {
                        node.addClass("shuffle-hover-highlight");
                      } else {
                        node.removeClass("shuffle-hover-highlight");
                      }
                    }
                  }
                };

                const handleMouseover = () => {
                  if (innerdata.type === "Runtime Argument") {
                    handleExecArgumentHover(true);
                  } else if (innerdata.type === "action") {
                    handleActionHover(true, innerdata.id);
                  }
                };

                const handleMouseOut = () => {
                  if (innerdata.type === "Runtime Argument") {
                    handleExecArgumentHover(false);
                  } else if (innerdata.type === "action") {
                    handleActionHover(false, innerdata.id);
                  }
                };

                var parsedPaths = [];

                if (innerdata.type === "workflow_variable") {
                  // Try to parse the value if it's a string that could be JSON
                  if (typeof innerdata.value === "string") {
                    try {
                      const parsedValue = JSON.parse(innerdata.value)
                      if (typeof parsedValue === "object") {
                        parsedPaths = GetParsedPaths(parsedValue, "");
                      }
                    } catch (e) {
                      // Not valid JSON, use the value directly
                      parsedPaths = GetParsedPaths(innerdata.value, "");
                    }
                  } else if (typeof innerdata.value === "object") {
                    parsedPaths = GetParsedPaths(innerdata.value, "");
                  }
                } else if (typeof innerdata.example === "object") {
                  parsedPaths = GetParsedPaths(innerdata.example, "");
                }

                const coverColor = "#82ccc3"

                return parsedPaths.length > 0 ? (
                  <span>
                    <NestedMenuItem
                      key={innerdata.name}
                      label={
                        <div style={{ display: "flex", marginLeft: 0 }}>
                          {icon} {innerdata.name}
                        </div>
                      }
                      parentMenuOpen={!!menuPosition}
                      style={{
                        color: theme.palette.text.primary,
                        minWidth: 250,
                        maxWidth: 250,
                        maxHeight: 50,
                        overflow: "hidden",
                        paddingTop: 8,
                      }}
                      onClick={() => {
                        console.log(innerdata.example)
                        handleItemClick([innerdata]);
                      }}
                    >
                      <Paper style={{ minHeight: 500, maxHeight: 500, minWidth: 275, maxWidth: 275, position: "fixed", top: menuPosition?.top - 100, left: menuPosition?.left + 250, padding: "10px 0px 10px 10px", overflow: "hidden", overflowY: "auto", border: "1px solid rgba(255,255,255,0.3)", }}>

                        <MenuItem
                          key={innerdata.name}
                          style={{
                            // backgroundColor: theme.palette.inputColor,
                            marginLeft: 15,
                            color: theme.palette.text.primary,
                            minWidth: 250,
                            maxWidth: 250,
                            padding: 0,
                            position: "relative",
                          }}
                          value={innerdata}
                          onMouseOver={() => {
                            //console.log("HOVER: ", pathdata);
                          }}
                          onClick={() => {
                            handleItemClick([innerdata]);
                          }}
                        >
                          <Typography variant="h6" style={{ paddingBottom: 5 }}>
                            {innerdata.name}
                          </Typography>
                        </MenuItem>

                        {parsedPaths.map((pathdata, index) => {
                          // FIXME: Should be recursive in here
                          //<VpnKeyIcon style={iconStyle} />
                          const icon =
                            pathdata.type === "value" ? (
                              <span style={{ marginLeft: 9, }} />
                            ) : pathdata.type === "list" ? (
                              <FormatListNumberedIcon style={{ marginLeft: 9, marginRight: 10, }} />
                            ) : (
                              <CircleIcon style={{ marginLeft: 9, marginRight: 10, color: coverColor }} />
                            );
                          //<ExpandMoreIcon style={iconStyle} />

                          const indentation_count = (pathdata.name.match(/\./g) || []).length + 1
                          const baseIndent = <div style={{ marginLeft: 20, height: 30, width: 1, backgroundColor: coverColor, }} />
                          //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0
                          const boxPadding = 0
                          const namesplit = pathdata.name.split(".")
                          const newname = namesplit[namesplit.length - 1]
                          return (
                            <MenuItem
                              key={pathdata.name}
                              style={{
                                // backgroundColor: theme.palette.inputColor,
                                color: theme.palette.text.primary,
                                minWidth: 250,
                                maxWidth: 250,
                                padding: boxPadding,
                              }}
                              value={pathdata}
                              onMouseOver={() => {
                                //console.log("HOVER: ", pathdata);
                              }}
                              onClick={() => {
                                handleItemClick([innerdata, pathdata]);
                              }}
                            >
                              <Tooltip
                                color="primary"
                                title={`Ex. value: ${pathdata.value}`}
                                placement="left"
                              >
                                <div style={{ display: "flex", height: 30, }}>
                                  {Array(indentation_count).fill().map((subdata, subindex) => {
                                    return (
                                      baseIndent
                                    )
                                  })}
                                  {icon} {newname}
                                  {pathdata.type === "list" ? <SquareFootIcon style={{ marginleft: 10, }} onClick={(e) => {

                                  }} /> : null}
                                </div>
                              </Tooltip>
                            </MenuItem>
                          );
                        })}
                      </Paper>
                    </NestedMenuItem>
                  </span>
                ) : (
                  <MenuItem
                    key={innerdata.name}
                    style={{
                      // backgroundColor: theme.palette.inputColor,
                      color: theme.palette.text.primary,
                      padding: "10px 12px", // Add padding here
                    }}
                    value={innerdata}
                    onMouseOver={() => handleMouseover()}
                    onMouseOut={() => {
                      handleMouseOut();
                    }}
                    onClick={() => {
                      handleItemClick([innerdata]);
                    }}
                  >
                    <Tooltip
                      color="primary"
                      title={`Value: ${innerdata.value}`}
                      placement="left"
                    >
                      <div style={{ display: "flex" }}>
                        {icon} {innerdata.name}
                      </div>
                    </Tooltip>
                  </MenuItem>
                );
              })}
            </Menu>
            )}
          </FormControl>
        )}
      </div>
    );
  };

  const menuItemStyle = {
    color: theme.palette.text.primary,
    backgroundColor: theme.palette.inputColor,
  };


  // Makes a list of items the user can choose from based on previous runs
  var availableArguments = []
  if (executionArgumentModalOpen && workflowExecutions.length > 0) {
    for (let executionKey in workflowExecutions) {
      if (availableArguments.length > 2) {
        break
      }


      const execution = workflowExecutions[executionKey]
      if (execution.execution_argument === undefined || execution.execution_argument === null || execution.execution_argument.length < 2) {
        continue
      }

      if (execution.execution_argument.includes("too large ")) {
        continue
      }

      if (execution.execution_argument === "{}" || execution.execution_argument === "[]") {
        continue
      }

      if (availableArguments.includes(execution.execution_argument)) {
        continue
      }


      availableArguments.push(execution.execution_argument)
    }
  }

  const handleAppAuthGroupCheckbox = (data) => {
    console.log("CHECKED: ", data)

    if (workflow.auth_groups === undefined || workflow.auth_groups === null) {
      workflow.auth_groups = []
    }

    const foundIndex = workflow.auth_groups.findIndex(auth => auth === data.id)
    if (foundIndex >= 0) {
      workflow.auth_groups.splice(foundIndex, 1)
    } else {
      workflow.auth_groups.push(data.id)
    }

    setWorkflow(workflow)
    setUpdate(Math.random())
  }

  const authgroupModal =
    <Dialog
      PaperComponent={PaperComponent}
      disableEnforceFocus={true}
      hideBackdrop={true}
      disableBackdropClick={true}
      style={{ pointerEvents: "none" }}
      PaperComponent={PaperComponent}
      aria-labelledby="draggable-dialog-title"
      open={authgroupModalOpen}
      PaperProps={{
        style: {
          padding: 30,
          pointerEvents: "auto",
          color: theme.palette.text.primary,
          minWidth: isMobile ? "90%" : 800,
          border: theme.palette.defaultBorder,

          borderRadius: theme.palette.borderRadius,
          backgroundColor: "black",
        },
      }}
      onClose={() => {
      }}
    >
      <Tooltip
        title="Close window"
        placement="top"
        style={{ zIndex: 10011 }}
      >
        <IconButton
          style={{ zIndex: 5000, position: "absolute", top: 34, right: 34 }}
          onClick={(e) => {
            e.preventDefault();
            setAuthgroupModalOpen(false)
          }}
        >
          <CloseIcon style={{ color: theme.palette.text.primary }} />
        </IconButton>
      </Tooltip>
      <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
        <span style={{ color: theme.palette.text.primary }}>Authgroup Selection</span>
      </DialogTitle>
      <DialogContent>
        <Typography variant="body1" color="textSecondary">
          Authgroups are a way to control how a workflow runs. If chosen, the workflow will use the groups on the nodes that have them selected. If three are chosen, the workflows runs three times. This is an experimental MSSP feature to handle multiple environments.
        </Typography>

        <Divider style={{ marginTop: 10, marginBottom: 20, }} />

        {authGroups.map((data, index) => {
          var checked = false
          if (workflow.auth_groups !== undefined && workflow.auth_groups !== null) {
            checked = workflow.auth_groups.includes(data.id)
          }

          if (data === undefined || data === null || data.app_auths === undefined || data.app_auths === null || data.app_auths.length === 0) {
            return null
          }

          return (
            <div
              style={{ display: 'flex', alignItems: 'center', marginBottom: '10px', marginLeft: '5px', cursor: "pointer", }}
              onClick={() => {
                handleAppAuthGroupCheckbox(data)
              }}
            >
              <Checkbox
                checked={checked}
                name={data.label}
              />

              <Typography variant="body1" color="textSecondary" style={{ minWidth: 200, maxWidth: 200, marginRight: 10, }}>
                {data.label}
              </Typography>

              <Typography variant="body1" color="textSecondary" style={{ minWidth: 150, maxWidth: 150, marginRight: 10, }}>
                {data.environment}
              </Typography>

              {data.app_auths.map((appAuth, index) => {
                if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") {
                  const foundImage = appAuthentication.find((auth) => auth.app.id === appAuth.app.id)
                  if (foundImage !== undefined) {
                    appAuth.app.large_image = foundImage.app.large_image

                    appAuth.app.name = foundImage.app.name
                  }
                }

                const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})`

                return (
                  <Tooltip
                    title={tooltip}
                  >
                    <img
                      key={index}
                      src={appAuth.app.large_image}
                      alt={appAuth.app.name}
                      style={{ width: 30, height: 30, marginRight: 5 }}
                    />
                  </Tooltip>
                )
              })}

            </div>
          )
        })}


        <Button
          variant="outlined"
          color="primary"
          onClick={() => {
            setAuthgroupModalOpen(false)
          }}
          style={{ marginTop: 20, marginBottom: 20 }}
        >
          Submit
        </Button>
      </DialogContent>
    </Dialog>

  const executionArgumentModal =
    <Dialog
      PaperComponent={PaperComponent}
      disableEnforceFocus={true}
      hideBackdrop={true}
      disableBackdropClick={true}
      style={{ pointerEvents: "none" }}
      PaperComponent={PaperComponent}
      aria-labelledby="draggable-dialog-title"
      open={executionArgumentModalOpen}
      PaperProps={{
        style: {
          padding: 30,
          pointerEvents: "auto",
          color: theme.palette.text.primary,
          minWidth: isMobile ? "90%" : 750,

          borderRadius: theme.palette.borderRadius,
          backgroundColor: "black",
        },
      }}
      onClose={() => {
      }}
    >
      <Tooltip
        title="Close window"
        placement="top"
        style={{ zIndex: 10011 }}
      >
        <IconButton
          style={{ zIndex: 5000, position: "absolute", top: 34, right: 34 }}
          onClick={(e) => {
            e.preventDefault();
            setExecutionArgumentModalOpen(false)
          }}
        >
          <CloseIcon style={{ color: theme.palette.text.primary }} />
        </IconButton>
      </Tooltip>
      <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
        <span style={{ color: theme.palette.text.primary }}>Provide an execution argument</span>
      </DialogTitle>
      <DialogContent>

        {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
          <div style={{ marginBottom: 5, }}>
            {workflow.input_questions.map((question, index) => {

              return (
                <div style={{ marginBottom: 5 }} key={index}>
                  {question.name}
                  <TextField
                    color="primary"
                    style={{ backgroundColor: theme.palette.inputColor, marginTop: 5, }}
                    multiLine
                    maxRows={2}
                    InputProps={{
                      style: {
                        height: "50px",
                        color: theme.palette.text.primary,
                        fontSize: "1em",
                      },
                    }}
                    fullWidth={true}
                    placeholder=""
                    id="emailfield"
                    margin="normal"
                    variant="outlined"
                    onBlur={(e) => {
                      var newtext = {}
                      if (executionText.length > 0) {
                        try {
                          newtext = JSON.parse(executionText)
                          // Check if list or object, then make it object only
                          if (Array.isArray(newtext)) {
                            newtext = {}
                          }
                        } catch (e) {
                          console.log("Error parsing JSON: ", e)
                        }
                      }

                      newtext[question.value] = e.target.value
                      setExecutionText(JSON.stringify(newtext))
                    }}
                  />
                </div>
              )
            })}

            <Button
              variant="outlined"
              color="primary"
              onClick={() => {
                executeWorkflow(executionText, workflow.start, lastSaved);
                setExecutionArgumentModalOpen(false)
              }}
              style={{ marginTop: 20, marginBottom: 20 }}
            >
              Run Workflow
            </Button>
          </div>
          :
          <div>
            <Typography variant="body1" color="textSecondary">
              At least one node in this workflow requires an execution argument ($exec). Please click one below, or provide a custom argument in the text field next to the run button.
            </Typography>

            <Divider style={{ marginTop: 10, marginBottom: 20, }} />
            {availableArguments.length > 0 ?
              <div>
                <Typography variant="body1" style={{}}>
                  Previously used arguments:
                </Typography>
                {availableArguments.map((data) => {
      
				  var defaultoutput = 
                      <Typography variant="body1" color="textSecondary">
                        {data}
                      </Typography>

				  const validate = validateJson(data, true)
      			  if (validate.valid === true) {
					  defaultoutput = 
						  <div style={{display: "flex", }}>
						  	  <Button
					  			variant="outlined"
					  			color="primary"
					  			style={{position: "sticky", top: 50, maxHeight: 40, maxWidth: 150, marginRight: 5, }}
					  			onClick={() => {
									setExecutionText(data)
									executeWorkflow(data, workflow.start, lastSaved)

									setExecutionArgumentModalOpen(false)
								}}
					  		  >Select</Button>
							  <ReactJson
								src={validate.result}
								theme={"summerfruit"}
								style={{
									padding: 5, 
									width: "98%",
									borderRadius: 5,
									border: "1px solid rgba(255,255,255,0.7)",
									overflowX: "auto",
								}}
								shouldCollapse={(jsonField) => {
								  return collapseField(jsonField)
								}}
								iconStyle={theme.palette.jsonIconStyle}
								collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
								displayArrayKey={false}
								displayDataTypes={false}
								name={false}
							  />
						  </div>
				  }

                  return (
                    <Paper 
					  style={{ padding: 10, marginTop: 10, backgroundColor: theme.palette.platformColor, maxHeight: 150, overflow: "auto", position: "relative", border: `2px solid rgba(255,255,255,0.3)`, }}
                      onClick={() => {
                        setExecutionText(data)
                        //executeWorkflow(data, workflow.start, lastSaved);

                        setExecutionArgumentModalOpen(false)
                      }}
                    >
                      <div style={{ height: "100%", width: 2, backgroundColor: "rgba(255, 255, 255, 0.5)", position: "absolute", left: 0, top: 0 }} />
					  {defaultoutput}
                    </Paper>
                  )
                })}
              </div>
              : null}

            <Button
              variant="outlined"
              color="secondary"
              onClick={() => {
                executeWorkflow(" ", workflow.start, lastSaved);
                setExecutionArgumentModalOpen(false)
              }}
              style={{ marginTop: 50, }}
            >
              Run without Runtime Argument 
            </Button>
          </div>
        }
      </DialogContent>
    </Dialog>

  const submitQueryModal = () => {
    const changeActionTextfield = document.getElementById("change-action-textfield")
    if (changeActionTextfield === undefined || changeActionTextfield === null) {
      setAiQueryModalOpen(false)
      toast.error("Failed to find textfield")
      return
    }

    if (changeActionTextfield.value === undefined || changeActionTextfield.value === null || changeActionTextfield.value === "") {
      toast("Please provide how you want formatting to happen")
      return
    }

    setAutocompleting(true)
    if (codeEditorModalOpen === true) {
      autoFormatCodemodal(changeActionTextfield.value)
    } else {
      aiSubmit(changeActionTextfield.value, undefined, undefined, selectedAction)
    }
  }

  const autoFormatCodemodal = (input) => {
    if (codeEditorModalOpen !== true) {
      toast.error("Code editor is not open")
      return
    }

    if (editorData.name === undefined || editorData.name === null || editorData.name === "") {
      toast.error("Failed to find editor field name")
      return
    }

    const codeeditor = document.getElementById("shuffle-codeeditor")
    if (codeeditor === undefined || codeeditor === null) {
      toast.error("Failed to find code editor html")
      return
    }

    const editorInstance = window?.ace?.edit("shuffle-codeeditor")
    if (editorInstance === undefined || editorInstance === null) {
      toast.error("Failed to find code editor instance")
      return
    }

    //console.log("ACE data: ", editorInstance.getValue())
    //editorInstance.setValue("HELO")

    // Should try to automatically fix this input
    console.log("Running AI input fixer: ", selectedResult)
    if (aiSubmit === undefined || selectedAction === undefined) {
      toast.error("Failed to find AI submit function")
      return
    }

    // Should remove params from selectedAction that aren't parameterName  
    var tmpAction = JSON.parse(JSON.stringify(selectedAction))
    var tmpParams = tmpAction.parameters.filter((param) => param.name === editorData.name)
    if (tmpParams.length !== 1) {
      toast.error("Failed to find correct parameter in action")
      return
    }

    tmpParams[0].value = editorInstance.getValue()
    tmpAction.parameters = tmpParams
    aiSubmit(input, undefined, undefined, tmpAction)
  }

  const aiQueryModal =
    <Dialog
      PaperComponent={PaperComponent}
      aria-labelledby="draggable-dialog-title"
      open={aiQueryModalOpen}
      PaperProps={{
        style: {
          color: theme.palette.text.primary,
          minWidth: isMobile ? "90%" : 450,
          border: theme.palette.defaultBorder,
          padding: 50,
          paddingBottom: 70,

          borderRadius: theme.palette.borderRadius,
          borderImage: "linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) 1",
        },
      }}
      onClose={() => {
        setAiQueryModalOpen(false)
      }}
    >
      <Tooltip
        title="Move window"
        placement="top"
        style={{ zIndex: 10011 }}
      >
        <IconButton
          id="draggable-dialog-title"
          style={{
            position: "absolute",
            top: 4,
            right: 34,
            cursor: "move",
          }}
          onClick={(e) => {
          }}
        >
          <DragIndicatorIcon style={{ color: theme.palette.text.primary }} />
        </IconButton>
      </Tooltip>
      <IconButton
        style={{
          position: "absolute",
          top: 6,
          right: 6,
          color: theme.palette.text.primary,
        }}
        onClick={() => {
          setAiQueryModalOpen(false)
        }}
      >
        <CloseIcon />
      </IconButton>
      <Typography variant="h6" color="textPrimary">
        Shuffle AI
      </Typography>
      <Typography variant="body2" color="textSecondary">
        What you write here will be fed to the Shuffle AI to generate a change for the selected action or field. Best used for when you are stuck with formatting. Uses your AI credits (resets monthly). <b>Beta feature.</b> Please give feedback to {supportEmail} {"<"}3

      </Typography>
      <TextField
        color="primary"
        autoFocus
        label={codeEditorModalOpen ? `How do you want to change the ${editorData?.name} field?` : ""}
        id="change-action-textfield"
        disabled={autoCompleting}
        minRows={1}
        maxRows={4}
        multiline
        style={{
          backgroundColor: theme.palette.inputColor,
          marginTop: 15,
        }}
        defaultValue={codeEditorModalOpen === true ? "" : selectedAction?.label !== undefined ? selectedAction.label.replaceAll("_", " ") : ""}
        fullWidth
        InputProps={{
          endAdornment: (
            <InputAdornment position="end">
              <Button
                color="primary"
                edge="end"
                disabled={autoCompleting}
                onClick={() => {
                  submitQueryModal()
                }}
              >
                {autoCompleting ? <CircularProgress size={20} /> : "Submit"}
                <SendIcon style={{ marginLeft: 10, }} />
              </Button>
            </InputAdornment>
          ),
          onKeyPress: (e) => {
            if (e.key === "Enter" && !e.shiftKey) {
              submitQueryModal()
            }
          },
        }}

      />
    </Dialog>

  const handleConditionFieldChange = (fieldType, value) => {
    if (fieldType === "source") {
      setSourceValue({
        ...sourceValue,
        value: value
      })

	  setUpdate(Math.random())
    } else if (fieldType === "destination") {
      setDestinationValue({
        ...destinationValue,
        value: value
      })

	  setUpdate(Math.random())
    }
  }


  const conditionsModal = (
    <Dialog
      PaperComponent={PaperComponent}
      disableEnforceFocus={true}
      hideBackdrop={true}
      disableBackdropClick={true}
      style={{ pointerEvents: "none" }}
      PaperComponent={PaperComponent}
      aria-labelledby="draggable-dialog-title"
      open={conditionsModalOpen}
      PaperProps={{
        sx: {
          pointerEvents: "auto",
          color: theme.palette.DialogStyle.color,
          minWidth: isMobile ? "90%" : "800px",
          border: theme.palette.defaultBorder,

          borderRadius: theme.palette.DialogStyle.borderRadius,
          backgroundColor: theme.palette.DialogStyle.backgroundColor,
          '& .MuiDialogContent-root': {
            backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
          },
          '& .MuiDialogTitle-root': {
            backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
          },
          '& .MuiDialogActions-root': {
            backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
          },
        },
      }}
      onClose={() => {
      }}
    >
      <span
        style={{
          position: "absolute",
          bottom: 10,
          left: 10,
          color: theme.palette.textColor,
          backgroundColor: 'inherit',
          zIndex: 10000,
		  fontSize: 12,
        }}
      >
        <b>PS: Conditions can't be used for loops [ .# ]. Use the filters list action.{" "}</b>
        <a
          rel="noopener noreferrer"
          target="_blank"
          href="/docs/workflows#conditions"
          style={{
            textDecoration: "none",
            color: theme.palette.linkColor,
          }}
        >
          Learn more
        </a>
      </span>
      <FormControl>
        <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
          <span style={{ color: theme.palette.text.primary }}>Condition</span>
        </DialogTitle>
        <DialogContent style={{}}>
          <div style={{ display: "flex" }}>
            <Tooltip
              color="primary"
              title={conditionValue.configuration ? "Opposite" : "Default"}
              placement="top"
            >
              <span
                style={{
                  margin: "auto",
                  height: 50,
                  marginBottom: "auto",
                  marginTop: "auto",
                  marginRight: 5,
                }}
              >
                <Button
                  color="primary"
                  variant={
                    conditionValue.configuration ? "contained" : "outlined"
                  }
                  style={{
                    margin: "auto",
                    height: 50,
                    marginBottom: "auto",
                    marginTop: "auto",
                    marginRight: 5,
                  }}
                  onClick={(e) => {
                    conditionValue.configuration =
                      !conditionValue.configuration;
                    setConditionValue(conditionValue);
                    setUpdate(Math.random());
                  }}
                >
                  {conditionValue.configuration ? "!" : "="}
                </Button>
              </span>
            </Tooltip>
            <div style={{ flex: "2" }}>
              <AppConditionHandler
                tmpdata={sourceValue}
                setData={setSourceValue}
                type={"source"}
                setExpansionModalOpen={setCodeEditorModalOpen}
                setActiveDialog={setActiveDialog}
                setEditorData={setEditorData}
              />
            </div>
            <div
              style={{
                flex: "1",
                margin: "auto",
                marginBottom: 0,
                marginLeft: 5,
                marginRight: 5,
              }}
            >
              <Button
                color="primary"
                variant="outlined"
                style={{ margin: "auto", height: 50, marginBottom: 50 }}
                fullWidth
                aria-haspopup="true"
                onClick={(e) => {
                  setVariableAnchorEl(e.currentTarget);
                }}
              >
                {conditionValue.value}
              </Button>
              <Menu
                id="simple-menu"
                keepMounted
                open={Boolean(variableAnchorEl)}
                anchorEl={variableAnchorEl}
                PaperProps={{
                  style: {
                    backgroundColor: theme.palette.surfaceColor,
                  },
                }}
                onClose={() => {
                  setVariableAnchorEl(null);
                }}
              >
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "equals";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"equals"}
                >
                  equals
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "does not equal";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"does not equal"}
                >
                  does not equal
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "startswith";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"starts with"}
                >
                  starts with
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "endswith";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"ends with"}
                >
                  ends with
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "contains";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"contains"}
                >
                  contains
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "contains_any_of";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"contains_any_of"}
                >
                  contains any of
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "matches regex";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"matches regex"}
                >
                  matches regex
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "larger than";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"larger than"}
                >
                  larger than
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "less than";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"less than"}
                >
                  less than
                </MenuItem>
                <MenuItem
                  style={menuItemStyle}
                  onClick={(e) => {
                    conditionValue.value = "is empty";
                    setConditionValue(conditionValue);
                    setVariableAnchorEl(null);
                  }}
                  key={"is empty"}
                >
                  is empty
                </MenuItem>
              </Menu>
            </div>
            <div style={{ flex: "2" }}>
              <AppConditionHandler
                tmpdata={destinationValue}
                setData={setDestinationValue}
                type={"destination"}
                setExpansionModalOpen={setCodeEditorModalOpen}
                setActiveDialog={setActiveDialog}
                setEditorData={setEditorData}
              />
            </div>
          </div>
        </DialogContent>
        <DialogActions sx={{pr: 3, pb: 1}}>
          <Button
            style={{ borderRadius: "0px" }}
            variant="text"
            onClick={() => {
              setConditionsModalOpen(false);
              setCodeEditorModalOpen(false)
              setSourceValue({});
              setConditionValue({});
              setDestinationValue({});
              navigate("")
            }}
            color="secondary"
          >
            Cancel
          </Button>
          <Button
            sx={{ borderRadius: "4px", px: 3 }}
            variant="contained"
            onClick={() => {
              setSelectedEdge({});
              navigate("")

              var data = {
                condition: conditionValue,
                source: sourceValue,
                destination: destinationValue,
              };

              setConditionsModalOpen(false);
              setCodeEditorModalOpen(false)
              if (selectedEdge.conditions === undefined || selectedEdge.conditions === null) {
                selectedEdge.conditions = [data];
              } else {
                const curedgeindex = selectedEdge.conditions.findIndex(
                  (data) => data.source.id === sourceValue.id
                )
                if (curedgeindex < 0) {
                  selectedEdge.conditions.push(data);
                } else {
                  selectedEdge.conditions[curedgeindex] = data;
                }
              }

              var label = "";
              if (selectedEdge.conditions.length === 1) {
                label = selectedEdge.conditions.length + " condition";
              } else if (selectedEdge.conditions.length > 1) {
                label = selectedEdge.conditions.length + " conditions";
              }

              var currentedge = cy.getElementById(selectedEdge.id);
              if (currentedge !== undefined && currentedge !== null && label !== undefined) {
                currentedge.data("label", label)
                //.label = label;
                //oldstartnode[0].data("isStartNode", false);
              }

              setSelectedEdge(selectedEdge);
              workflow.branches[selectedEdgeIndex] = selectedEdge;
              setWorkflow(workflow);
            }}
            color="primary"
          >
            Submit
          </Button>
        </DialogActions>
      </FormControl>
    </Dialog>
  );

  const EdgeSidebar = () => {
    const ConditionHandler = (condition, index) => {
      const [open, setOpen] = React.useState(false);
      const [anchorEl, setAnchorEl] = React.useState(null);

      const duplicateCondition = (conditionIndex) => {
        var newEdge = JSON.parse(
          JSON.stringify(selectedEdge.conditions[conditionIndex])
        );
        const newUuid = uuidv4();
        newEdge.condition.id = newUuid;
        newEdge.source.id = newUuid;
        newEdge.destination.id = newUuid;
        selectedEdge.conditions.push(newEdge);

        setUpdate(Math.random());
      };

      const deleteCondition = (conditionIndex) => {
        console.log(selectedEdge);
        if (selectedEdge.conditions.length === 1) {
          selectedEdge.conditions = [];
        } else {
          selectedEdge.conditions.splice(conditionIndex, 1);
        }

        setSelectedEdge(selectedEdge);
        setOpen(false);
        setUpdate(Math.random());
      };

      const paperVariableStyle = {
        minHeight: 75,
        maxHeight: 75,
        minWidth: "100%",
        maxWidth: "100%",
        marginTop: "5px",
        color: theme.palette.text.primary,
        backgroundColor: theme.palette.surfaceColor,
        cursor: "pointer",
        display: "flex",
      };

      const menuClick = (event) => {
        setOpen(!open);
        setAnchorEl(event.currentTarget);
      };

      return (
        <Paper
          key={condition.condition.id}
          square
          style={paperVariableStyle}
          onClick={() => { 
  			setLastSaved(false)
		  }}
        >
          <div
            style={{
              marginLeft: "10px",
              marginTop: "5px",
              marginBottom: "5px",
              width: 2,
              backgroundColor: yellow,
              marginRight: "5px",
            }}
          />
          <div style={{ display: "flex", width: "100%" }}>
            <div
              style={{ flex: "10", display: "flex" }}
              onClick={() => {
                setSourceValue(condition.source);
                setConditionValue(condition.condition);
                setDestinationValue(condition.destination);
                setConditionsModalOpen(true);
                setCodeEditorModalOpen(false)
              }}
            >
              <div
                style={{
                  flex: 1,
                  textAlign: "left",
                  marginTop: "15px",
                  marginLeft: "10px",
                  overflow: "hidden",
                  maxWidth: 72,
                }}
              >
                {condition.source.value}
              </div>
              <Divider
                style={{
                  height: "100%",
                  width: "1px",
                  marginLeft: "5px",
                  marginRight: "5px",
                  backgroundColor: "rgb(91, 96, 100)",
                }}
              />
              <div
                style={{
                  flex: 1,
                  textAlign: "center",
                  marginTop: "15px",
                  overflow: "hidden",
                  maxWidth: 72,
                }}
                onClick={() => { }}
              >
                {condition.condition.value}
              </div>
              <Divider
                style={{
                  height: "100%",
                  width: "1px",
                  marginLeft: "5px",
                  marginRight: "5px",
                  backgroundColor: "rgb(91, 96, 100)",
                }}
              />
              <div
                style={{
                  flex: 1,
                  textAlign: "left",
                  marginTop: "auto",
                  marginBottom: "auto",
                  marginLeft: "10px",
                  overflow: "hidden",
                  maxWidth: 72,
                }}
              >
                {condition.destination.value}
              </div>
            </div>
            <div style={{ flex: "1", marginLeft: "0px" }}>
              <IconButton
                aria-label="more"
                aria-controls="long-menu"
                aria-haspopup="true"
                onClick={menuClick}
                style={{ color: theme.palette.text.primary }}
              >
                <MoreVertIcon />
              </IconButton>
              <Menu
                id="long-menu"
                anchorEl={anchorEl}
                keepMounted
                open={open}
                PaperProps={{
                  style: {
                    backgroundColor: theme.palette.surfaceColor,
                  },
                }}
                onClose={() => {
                  setOpen(false);
                  setAnchorEl(null);
                }}
              >
                <MenuItem
                  style={{ backgroundColor: theme.palette.inputColor, color: theme.palette.text.primary }}
                  onClick={() => {
                    duplicateCondition(index);
                  }}
                  key={"Duplicate"}
                >
                  {"Duplicate"}
                </MenuItem>
                <MenuItem
                  style={{ backgroundColor: theme.palette.inputColor, color: theme.palette.text.primary }}
                  onClick={() => {
                    setOpen(false);
                    deleteCondition(index);
                  }}
                  key={"Delete"}
                >
                  {"Delete"}
                </MenuItem>
              </Menu>
            </div>
          </div>
        </Paper>
      );
    };

    var injectedData = <div></div>;

    if (selectedEdge.conditions !== undefined && selectedEdge.conditions !== null && selectedEdge.conditions.length > 0) {
      injectedData = selectedEdge.conditions.map((condition, index) => {
        return ConditionHandler(condition, index);
      });
    }

    // Startnode = dest node
    const conditionsDisabled = false

    const conditionId = uuidv4();
    const sourceAction = cy.getElementById(selectedEdge.source)
    const targetAction = cy.getElementById(selectedEdge.target)
    const sourceImage = sourceAction?.data()?.large_image
    const targetImage = targetAction?.data()?.large_image
    return (
      <div style={appApiViewStyle}>
        <div style={{}}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}>
            <h3 style={{ marginBottom: 5, }}>
              Conditions
            </h3>
            <div style={{
              display: "flex",
              alignItems: "center",
              paddingTop: "10px",
              paddingRight: "3px",
              gap: "5px"
            }}>
              {selectedEdge?.source && workflow?.actions ? 
                <img 
                  src={sourceImage || ""}
                  alt="Source"
                  style={{
                    width: 32,
                    height: 32,
                    borderRadius: "50%",
                    marginLeft: 10,
                  }}
                />
                : null
              }

              {/* Add arrow icon */}
              {
                selectedEdge && Object.keys(selectedEdge).length > 0 ?
                <ArrowForwardIcon style={{ 
                  color: theme.palette.textColor,
                  fontSize: 20,
                }} />
                : null
              }

              {/* Destination node image */}
              {selectedEdge?.target && workflow?.actions ?
                <img
                  src={targetImage || ""}
                  alt="Destination" 
                  style={{
                    width: 32,
                    height: 32,
                    borderRadius: "50%",
                  }}
                />
                : null
              }
            </div>
          </div>
          <a
            rel="noopener noreferrer"
            target="_blank"
            href="https://shuffler.io/docs/workflows#conditions"
            style={{ textDecoration: "none", color: theme.palette.linkColor }}
          >
            What are conditions?
          </a>
        </div>
        <Divider
          style={{
            marginBottom: "10px",
            marginTop: "10px",
            height: "1px",
            width: "100%",
            backgroundColor: "rgb(91, 96, 100)",
          }}
        />
        <div>Conditions</div>
        {injectedData}

        <Button
          style={{ margin: "auto", marginTop: "10px" }}
          color="secondary"
          variant="outlined"
          onClick={() => {
            if (conditionsModalOpen) {
              return
            }

  			setLastSaved(false)
            setSourceValue({
              name: "source",
              value: "",
              variant: "STATIC_VALUE",
              action_field: "",
              id: conditionId,
            });
            setConditionValue({
              name: "condition",
              value: "equals",
              id: conditionId,
            });
            setDestinationValue({
              name: "destination",
              value: "",
              variant: "STATIC_VALUE",
              action_field: "",
              id: conditionId,
            });

            setConditionsModalOpen(true);
          }}
          fullWidth
        >
          New condition
        </Button>

        {/* Check if dest is the same as start */}
        {conditionsDisabled ?
          <Typography variant="body1">
            Conditions are unavailable between triggers and the startnode.
          </Typography>
          : null}

        <div style={{ position: "absolute", bottom: 15, width: "90%", margin: "auto", }}>

          <Button
            style={{ margin: "auto", marginTop: "15px" }}
            color="secondary"
            fullWidth
            variant="outlined"
            onClick={() => {
              // Change Direction of the branch target/source
              const foundBranch = cy.getElementById(selectedEdge.id)
              if (foundBranch !== undefined && foundBranch !== null) {
                const source = foundBranch.data("source")
                const target = foundBranch.data("target")

                var branchdata = JSON.parse(JSON.stringify(foundBranch.data()))
                const startNode = workflow.start
                if (source === startNode) {
                  toast("Can't point to Start Node")
                } else {
  				  setLastSaved(false)
                  const newid = uuidv4()
                  branchdata.source = target
                  branchdata.target = source
                  branchdata.id = newid
                  branchdata._id = newid

                  foundBranch.remove()

                  cy.add({
                    group: "edges",
                    source: target,
                    target: source,
                    data: branchdata,
                  })
                  selectedEdge.id = newid
                  setSelectedEdge(selectedEdge)
                  toast("Branch direction changed!")
                }
              }
            }}
            fullWidth
          >
            <SwapHorizIcon style={{ marginRight: 10 }} />
            Flip Branch
          </Button>
          {/*<Button
			  style={{ margin: "auto", }}
			  color="secondary"
			  fullWidth
			  variant="outlined"
			  onClick={() => {
				// Delete the branch
			  }}
			  fullWidth
			>
				Re-attach branch
			</Button>
			<Button
			  style={{ margin: "auto", }}
			  color="secondary"
			  fullWidth
			  variant="outlined"
			  onClick={() => {
				// Delete the branch
			  }}
			  fullWidth
			>
				Disable Path
			</Button>
			*/}
          <Button
            style={{ margin: "auto", marginTop: 20, }}
            color="secondary"
            fullWidth
            variant="outlined"
            onClick={() => {
              // Delete the branch
              const foundBranch = cy.getElementById(selectedEdge.id)
              if (foundBranch !== undefined && foundBranch !== null) {
                foundBranch.remove()
              }
              setConditionsModalOpen(false)
              setSelectedEdge({})
            }}
            fullWidth
          >
            <DeleteIcon style={{ marginRight: 10, }} />
            Delete Branch
          </Button>
        </div>
      </div>
    );
  };

  const handleWorkflowSelectionUpdate = (e, isUserinput) => {

    if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
      console.log("Returning as there's no id. Value: ", e.target.value);
      return null
    }

    const paramIndex = isUserinput === true ? 5 : 0

    console.log("USERINPUT: ", paramIndex, workflow.triggers[selectedTriggerIndex])
    if (workflow.triggers[selectedTriggerIndex].parameters[paramIndex] === undefined || workflow.triggers[selectedTriggerIndex].parameters[paramIndex] === null) {
      workflow.triggers[selectedTriggerIndex].parameters[paramIndex] = {
        "name": "subflow",
        "value": "",
      }
    }

    setUpdate(Math.random());
    workflow.triggers[selectedTriggerIndex].parameters[paramIndex].value = e.target.value.id;
    setSubworkflow(e.target.value);

    // Sets the startnode
    if (e.target.value.id !== workflow.id && e.target.value.id.length > 0) {

      const startnode = e?.target?.value?.actions?.find((action) => action.id === e.target.value.start);


      if (startnode !== undefined && startnode !== null) {
        setSubworkflowStartnode(startnode);

        if (paramIndex === 0) {
          try {
            workflow.triggers[selectedTriggerIndex].parameters[3].value = startnode.id;
          } catch {
            workflow.triggers[selectedTriggerIndex].parameters[3] = {
              name: "startnode",
              value: startnode.id,
            };
          }
        }

        //setWorkflow(workflow);
      }
    } else {
      console.log("WORKFLOW: ", workflow);
    }

    setWorkflow(workflow);
  }
  // Function to transform the data
  const transformAuthData = (authData) => {
    const transformedData = {};

    let subflowId = workflow.triggers[selectedTriggerIndex].parameters[0].value;

    // get the apps used in "find your workflow"
    if (subflowId === "" && subflowId === undefined && subflowId === null) {
      console.log("subflow is empty")
      return {};
    }

    let workflowApps = usedSubflowApps;

    if (workflowApps === undefined || workflowApps === null) {
      console.log("workflow apps is empty");
      return {};
    }

    // get the app ids
    // let appIdsInWorkflow = [...new Set(workflowApps.map(app => app.app_id))];
    let appIdsInWorkflow = [];

    Object.entries(workflowApps).forEach(([key, value]) => {
      appIdsInWorkflow.push(value.app_id);
    })

    appIdsInWorkflow = [...new Set(appIdsInWorkflow)];

    // loop through the authData and create transformedData which looks like:
    // appId: [auth1, auth2, ...]
    authData.forEach((auth) => {
      const { app } = auth;
      const appId = app.id;

      // check if the app is used in the workflow
      if (appIdsInWorkflow.includes(appId)) {
        if (transformedData[appId] === undefined) {
          transformedData[appId] = [];
        }

        transformedData[appId].push(auth);
      }

    });

    return transformedData;

  };

  const AppAuthSelector = ({ appAuthData }) => {
    const [selectedAuth, setSelectedAuth] = useState("");
    const [transformedAuthData, setTransformedAuthData] = useState({});

    useEffect(() => {
      setTransformedAuthData(transformAuthData(appAuthData));
    }, [appAuthData, selectedAuth]);

    const handleShowingValue = (appName) => {
      let mappingWithName = {}
      let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("="))
      if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) {
        return "no-overrides";
      }

      for (let i = 0; i < listWithValues.length; i++) {
        mappingWithName[listWithValues[i][0]] = listWithValues[i][1]
      }

      if (mappingWithName[appName] !== undefined) {
        return mappingWithName[appName];
      }

      return "no-overrides";
    }

    const handleSelectChange = (appName, appId, event) => {
      const authId = event.target.value || "no-override";

      if (authId === "no-override") {
        // remove the override parameter
        let oldValue = workflow.triggers[selectedTriggerIndex].parameters[5].value;
        // replace from appName= to the next ;
        let newValue = oldValue.replace(new RegExp(appName + "=[^;]*;"), "");

        workflow.triggers[selectedTriggerIndex].parameters[5].value = newValue
        setSelectedAuth("");
        return
      }

      const auth = transformedAuthData[appId].find((auth) => auth.id === authId);

      if (auth === undefined) {
        setSelectedAuth("");
        return;
      }

      // // check if the trigger already has an override parameter
      // for (let i = 0; i < workflow.triggers[selectedTriggerIndex].parameters.length; i++) {
      //   // if name includes the app id
      //   if (workflow.triggers[selectedTriggerIndex].parameters[i].name.includes(appId + "_override")) {
      //     // update the value
      //     workflow.triggers[selectedTriggerIndex].parameters[i].value = auth.id;
      //     setSelectedAuth(auth.id);
      //     return;
      //   }
      // }

      if (workflow.triggers[selectedTriggerIndex].parameters[5] === undefined || workflow.triggers[selectedTriggerIndex].parameters[5] === null) {
        workflow.triggers[selectedTriggerIndex].parameters[5] = {
          name: "auth_override",
          value: "",
        };
      }

      let authGroupValue = workflow.triggers[selectedTriggerIndex].parameters[5].value;

      if (authGroupValue === undefined || authGroupValue === null || authGroupValue === "") {
        workflow.triggers[selectedTriggerIndex].parameters[5].value = appName + "=" + auth.id + ";";
      } else {
        // check if the app is already in the list
        if (authGroupValue.includes(appName)) {
          let oldValue = workflow.triggers[selectedTriggerIndex].parameters[5].value;
          let newValue = oldValue.replace(new RegExp(appName + "=[^;]*;"), appName + "=" + auth.id + ";");
          workflow.triggers[selectedTriggerIndex].parameters[5].value = newValue;
        } else {
          workflow.triggers[selectedTriggerIndex].parameters[5].value += appName + "=" + auth.id + ";";
        }
      }

      // workflow.triggers[selectedTriggerIndex].parameters.push({
      //   name: auth.label + "_" + auth.app.id + "_override",
      //   value: auth.id,
      // });
      setSelectedAuth(auth.id);
    }

    return (
      <div className="auth-container" style={{ padding: '20px', backgroundColor: '#26292D', borderRadius: '8px' }}>
        {Object.entries(transformedAuthData).map(([appId, authList]) => {
          if (authList === undefined || authList === null || authList.length < 2) {
            return null;
          }

          return (
            <div key={appId} className="auth-item" style={{ marginBottom: '20px' }}>
              <label className="auth-label" style={{ display: 'block', marginBottom: '10px', fontWeight: 'bold', color: '#E8E8E8' }}>
                {authList[0].app.name} Authentication:
              </label>
              <select
                value={handleShowingValue(authList[0].app.name)}
                onChange={(e) => handleSelectChange(authList[0].app.name, authList[0].app.id, e)}
                className="auth-select"
                style={{
                  width: '100%',
                  padding: '10px',
                  fontSize: '16px',
                  borderRadius: '4px',
                  border: '1px solid #555',
                  backgroundColor: theme.palette.inputColor,
                  color: '#E8E8E8',
                  boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
                  transition: 'border-color 0.2s, box-shadow 0.2s',
                }}
                onFocus={(e) => e.target.style.borderColor = '#007BFF'}
                onBlur={(e) => e.target.style.borderColor = '#555'}
              >
                <option value="no-override">No override</option>
                {authList.flatMap((auth) =>
                  <option
                    key={auth.id}
                    value={auth.id}
                    style={{
                      backgroundColor: theme.palette.inputColor,
                      fontSize: "1.2em",
                    }}
                  >
                    {auth.label}
                  </option>
                )}
              </select>
            </div>
          )
        })}
      </div>
    );
  };


  const iconStyle = {
    marginRight: 15,
  };


  if (selectedTrigger !== undefined && selectedTrigger !== null && Object.getOwnPropertyNames(selectedTrigger)?.length > 0) {
    if (workflow.triggers[selectedTriggerIndex] === undefined) {
      return null;
    }

    if (
      workflow.triggers[selectedTriggerIndex].parameters === undefined ||
      workflow.triggers[selectedTriggerIndex].parameters === null ||
      workflow.triggers[selectedTriggerIndex].parameters.length === 0
    ) {
      workflow.triggers[selectedTriggerIndex].parameters = [];
      workflow.triggers[selectedTriggerIndex].parameters[0] = {
        name: "workflow",
        value: "",
      };
      workflow.triggers[selectedTriggerIndex].parameters[1] = {
        name: "argument",
        value: "",
      };
      workflow.triggers[selectedTriggerIndex].parameters[2] = {
        name: "user_apikey",
        value: "",
      };
      workflow.triggers[selectedTriggerIndex].parameters[3] = {
        name: "startnode",
        value: "",
      };
      workflow.triggers[selectedTriggerIndex].parameters[4] = {
        name: "check_result",
        value: "false",
      };
      workflow.triggers[selectedTriggerIndex].parameters[5] = {
        name: "auth_override",
        value: "",
      };

    }

    var handleSubflowStartnodeSelection = (e) => {
      setSubworkflowStartnode(e.target.value);

      if (e.target.value === null || e.target.value === undefined) {
        return
      }

      const branchId = uuidv4();
      const newbranch = {
        source_id: workflow.triggers[selectedTriggerIndex].id,
        destination_id: e.target.value.id,
        source: workflow.triggers[selectedTriggerIndex].id,
        target: e.target.value.id,
        has_errors: false,
        id: branchId,
        _id: branchId,
        label: "Subflow",
        decorator: true,
      };

      if (workflow.visual_branches !== undefined) {
        if (workflow.visual_branches === null) {
          workflow.visual_branches = [newbranch];
        } else if (workflow.visual_branches.length === 0) {
          workflow.visual_branches.push(newbranch);
        } else {
          const foundIndex = workflow.visual_branches.findIndex(
            (branch) => branch.source_id === newbranch.source_id
          );

          if (foundIndex !== -1) {
            const currentEdge = cy.getElementById(
              workflow.visual_branches[foundIndex].id
            );
            if (
              currentEdge !== undefined &&
              currentEdge !== null
            ) {
              currentEdge.remove();
            }
          }

          workflow.visual_branches.splice(foundIndex, 1);
          workflow.visual_branches.push(newbranch);
        }
      }

      if (workflow.id === subworkflow.id) {
        const cybranch = {
          group: "edges",
          source: newbranch.source_id,
          target: newbranch.destination_id,
          id: branchId,
          data: newbranch,
        };

        cy.add(cybranch);
      }

      try {
        workflow.triggers[selectedTriggerIndex].parameters[3].value = e.target.value.id
      } catch(e) {
		  console.log("Error: ", e)
          workflow.triggers[selectedTriggerIndex].parameters[3] =
          {
            name: "startnode",
            value: e.target.value.id,
          };
      }

      setWorkflow(workflow)
	  setUpdate(Math.random())
    }
  }

  const handleMenuClose = () => {
    setUpdate(Math.random());
    setMenuPosition(null);
  };

  const handleItemClick = (values) => {
  console.log("VALUES: ", values)
  if (values === undefined || values === null || values.length === 0) {
    return;
  }

  // Get the base autocomplete value and normalize it
  let toComplete = values[0].autocomplete.toLowerCase().replaceAll(" ", "_");
  
  // Add $ prefix if not already present
  if (!toComplete.startsWith("$")) {
    toComplete = "$" + toComplete;
  }

  // Add any additional path components
  for (let key in values) {
    if (key === "0" || !values[key].autocomplete) {
      continue; 
    }

    toComplete += values[key].autocomplete;
  }

  var foundField;
  // Update the field value
  if(selectedTrigger?.trigger_type === "SUBFLOW"){
    foundField = document.getElementById("subflow_exec_field");
  }else if(selectedTrigger?.trigger_type === "USERINPUT"){
    foundField = document.getElementById("userinput_info_field");
  }

  if (foundField) {
    // Get current cursor position
    const cursorPos = foundField.selectionStart;
    const currentValue = foundField.value;

    // Insert the new value at cursor position
    const newValue = currentValue.slice(0, cursorPos) + toComplete + currentValue.slice(cursorPos);
    foundField.value = newValue;
    
    // Update state
    setSelectedTriggerValue(newValue);
    if(selectedTrigger?.trigger_type === "SUBFLOW"){
    workflow.triggers[selectedTriggerIndex].parameters[1].value = newValue
    }else if(selectedTrigger?.trigger_type === "USERINPUT"){
      workflow.triggers[selectedTriggerIndex].parameters[0].value = newValue
    }

    // Set cursor position after inserted text
    foundField.setSelectionRange(cursorPos + toComplete.length, cursorPos + toComplete.length);
  }

  setWorkflow(workflow);
  setUpdate(Math.random());
  setShowDropdown(false);
  setMenuPosition(null);
};

  const subflowtypes = [
    {
      name: "Any",
    },
    {
      name: "Enrich",
    },
    {
      name: "Ticket Creation",
    }
  ]

  const handleTriggerParamChange = (triggerId, triggerField, newData) => {
	var updateFail = "" 

    if (workflow !== undefined && workflow !== null) {  
        // Find the trigger with matching id 
      const triggerIndex = workflow?.triggers?.findIndex(trigger => trigger.id === triggerId);
      if (triggerIndex >= 0) {
        // Find the parameter with matching name
        const paramIndex = workflow.triggers[triggerIndex].parameters?.findIndex(param => param.name === triggerField);
        if (paramIndex >= 0) {
          // Update the parameter value
          workflow.triggers[triggerIndex].parameters[paramIndex].value = newData;
          
          // Update workflow state to trigger re-render
          setWorkflow(workflow);
          setSelectedTriggerValue(newData)
          setLastSaved(false);
		  setUpdate(Math.random())
        } else {
			updateFail = "Parameter is undefined or null"
		}
	  } else {
		  updateFail = "Trigger is undefined or null"
	  }
	} else {
		updateFail = "Workflow is undefined or null"
	}

	if (updateFail !== "") {
		toast.error(updateFail + " - Failed to update subflow parameter value. Please try again.")
	}
  }

  const SubflowSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "SUBFLOW" ? null :
    <div style={appApiViewStyle}>
      <span style={{ display: "flex", }}>
        <h3 style={{ marginBottom: "5px", flex: 3, }}>
          {selectedTrigger.app_name}
        </h3>
        <Tooltip placement="left">
          <Select
            MenuProps={{
              disableScrollLock: true,
            }}
            value={selectedTrigger.tags !== undefined && selectedTrigger.tags !== null && selectedTrigger.tags.length > 0 ? selectedTrigger.tags[0] : "Any"}
            onChange={(event) => {
              if (selectedTrigger.tags === undefined || selectedTrigger.tags === null) {
                selectedTrigger.tags = [event.target.value]
              } else {
                if (selectedTrigger.tags.includes(event.target.value)) {
                  return
                }

                if (selectedTrigger.tags.length > 0) {
                  selectedTrigger.tags = []
                }

                selectedTrigger.tags.push(event.target.value)
              }

              setSelectedTrigger(selectedTrigger)
              setUpdate(Math.random())
            }}
            style={{
              marginTop: 10,
              flex: 1,
              backgroundColor: theme.palette.inputColor,
              color: theme.palette.text.primary,
              height: 35,
              marginleft: 10,
              borderRadius: theme.palette?.borderRadius,
              color: "rgba(255,255,255,0.4)",
            }}
            SelectDisplayProps={{
              style: {
              },
            }}
          >
            {subflowtypes.map((data, index) => {
              return (
                <MenuItem
                  key={index}
                  style={{
                    backgroundColor: theme.palette.inputColor,
                    color: theme.palette.text.primary,
                  }}
                  value={data.name}
                >
                  {data.name}
                </MenuItem>
              );
            })}
          </Select>
        </Tooltip>
      </span>
      <a
        rel="noopener noreferrer"
        target="_blank"
        href="https://shuffler.io/docs/triggers#subflow"
        style={{ textDecoration: "none", color: theme.palette.linkColor }}
      >
        What are subflows?
      </a>
      <Divider
        style={{
          marginBottom: "10px",
          marginTop: "10px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div style={{ display: "flex" }}>
        <div style={{ flex: 5 }}>
          <Typography>Name</Typography>
          <TextField
            style={{
              backgroundColor: theme.palette.inputColor,
              color: theme.palette.text.primary,
              borderRadius: theme.palette?.borderRadius,
              marginTop: 3,
            }}
            InputProps={{
              style: {
                color: theme.palette.text.primary
              }
            }}
            size="small"
            fullWidth
            color="primary"
            placeholder={selectedTrigger.label}
            defaultValue={selectedTrigger?.label}
            onChange={selectedTriggerChange}
			disabled={selectedTrigger?.parent_controlled === true && workflow?.parentorg_workflow?.length > 0}
          />
        </div>
        <div>
          <div style={{ flex: 1, marginLeft: 5, }}>
            <Tooltip
              color="primary"
              title={"Delay before subflow runs (in seconds)"}
              placement="top"
            >
              <span>
                <Typography>Delay</Typography>
                <TextField
                  style={{
                    backgroundColor: theme.palette.inputColor,
                    color: theme.palette.text.primary,
                    marginTop: 3,
                    maxWidth: 50,
                  }}
                  InputProps={{
                    style: {
                      color: theme.palette.text.primary
                    }
                  }}
                  size="small"
                  placeholder={selectedTrigger.execution_delay}
                  defaultValue={selectedTrigger?.execution_delay || 0}
                  onChange={(event) => {
                    if (isNaN(event.target.value)) {
                      console.log("NAN: ", event.target.value)
                      return
                    }

                    const parsedNumber = parseInt(event.target.value)
                    if (parsedNumber > 86400) {
                      console.log("Max number is 1 day (86400)")
                      return
                    }

                    selectedTrigger.execution_delay = parseInt(event.target.value)
                    setSelectedTrigger(selectedTrigger)
                  }}
                />
              </span>
            </Tooltip>
          </div>
        </div>
      </div>
      <FormControlLabel
        control={
          <Checkbox
            checked={
              workflow.triggers[selectedTriggerIndex].parameters[4] !==
              undefined &&
              workflow.triggers[selectedTriggerIndex].parameters[4]
                .value === "true"
            }
            onChange={() => {
              const newvalue = workflow.triggers[selectedTriggerIndex].parameters[4] === undefined || workflow.triggers[selectedTriggerIndex].parameters[4].value === "false" ? "true" : "false";
              workflow.triggers[selectedTriggerIndex].parameters[4] = {
                name: "check_result",
                value: newvalue,
              };

              setWorkflow(workflow);
              setUpdate(Math.random());
            }}
            color="primary"
            value="Wait for results"
          />
        }
        style={{ marginTop: 10 }}
        label={<div style={{ color: theme.palette.text.primary }}>Wait for results</div>}
      />
      <div style={{ flex: "6", marginTop: 10, }}>
        <div>
          <div style={{ display: "flex" }}>
            <div
              style={{
                marginTop: "20px",
                marginBottom: "7px",
                display: "flex",
                flex: 5,
              }}
            >
              <div style={{ flex: "10" }}>
                <b>Select a workflow to run</b>
              </div>
            </div>
          </div>

          {workflows === undefined ||
            workflows === null ||
            workflows.length === 0 ? 
			  <CircularProgress color="secondary" style={{ margin: "auto", marginTop: 10, width: 25, height: 25, }} />
			: (
            <Autocomplete
              id="subflow_search"
              autoHighlight
              value={subworkflow || "No Workflow Selected"}
              classes={{ inputRoot: classes.inputRoot }}
              ListboxProps={{
                style: {
                  backgroundColor: theme.palette.inputColor,
                  color: theme.palette.text.primary,
                },
              }}
              sx={{
                '& .MuiOutlinedInput-root': {
                  height: 40, // Adjust the input height
                },
                '& .MuiAutocomplete-input': {
                  padding: '8px', // Adjust the text padding
                },
              }}
              getOptionSelected={(option, value) => option.id === value.id}
              getOptionLabel={(option) => {
                if (
                  option === undefined ||
                  option === null ||
                  option.name === undefined ||
                  option.name === null
                ) {
                  return "No Workflow Selected";
                }

                const newname = (
                  option.name.charAt(0).toUpperCase() + option.name.substring(1)
                ).replaceAll("_", " ");
                return newname;
              }}
              options={workflows}
              fullWidth
              style={{
                backgroundColor: theme.palette.inputColor,
                borderRadius: theme.palette?.borderRadius,
              }}
              onChange={(event, newValue) => {
                setLastSaved(false)
                console.log("Found value: ", newValue)

                var parsedinput = { target: { value: newValue } }

                // For variables
                if (typeof newValue === 'string' && newValue.startsWith("$")) {
                  parsedinput = {
                    target: {
                      value: {
                        "name": newValue,
                        "id": newValue,
                        "actions": [],
                        "triggers": [],
                      }
                    }
                  }
                }

                handleWorkflowSelectionUpdate(parsedinput)
              }}
              renderOption={(props, data, state) => {
                if (data.id === workflow.id) {
                  data = workflow;
                }

                //key={index}
                return (
                  <Tooltip arrow placement="left" title={
                    <span style={{}}>
                      {data.image !== undefined && data.image !== null && data.image.length > 0 ?
                        <img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, maxWidth: 285, borderRadius: theme.palette?.borderRadius, }} />
                        : null}
                      <Typography>
                        Choose Subflow '{data.name}'
                      </Typography>
                    </span>
                  }>
                    <MenuItem
                      style={{
                        backgroundColor: theme.palette.inputColor,
                        color: data.id === workflow.id ? "red" : theme.palette.text.primary,
                      }}
                      value={data}
                      onClick={() => {
                        getWorkflowApps(data.id);
                        handleWorkflowSelectionUpdate({
                          target: {
                            value: data
                          }
                        })
                        document.activeElement.blur();
                      }}
                    >
                      <PolylineIcon style={{ marginRight: 8 }} />
                      {data.name}
                    </MenuItem>
                  </Tooltip>
                )
              }}
              renderInput={(params) => {
                return (
				  <div style={{ display: "flex", }}>
                    <TextField
                      style={theme.palette.textFieldStyle}
                      {...params}
                      label="Find your workflow"
                      variant="outlined"
                    />
					{workflow.triggers[selectedTriggerIndex].parameters[0].value
					  .length === 0 ? null : workflow.triggers[selectedTriggerIndex]
						.parameters[0].value === props.match.params.key ? 
						null
						: (
					  <div style={{ marginLeft: 5, }}>
						<a
						  rel="noopener noreferrer"
						  href={`/workflows/${workflow.triggers[selectedTriggerIndex].parameters[0].value}`}
						  target="_blank"
						  style={{
							textDecoration: "none",
							color: "#FF8544",
							marginLeft: 5,
							marginTop: 10,
						  }}
						>
						  <OpenInNewIcon />
						</a>
					  </div>
					)}
				  </div>
                )
              }}
            />
          )}

          {subworkflow === undefined ||
            subworkflow === null ||
            subworkflow.id === undefined ||
            subworkflow.actions === null ||
            subworkflow.actions === undefined ||
            subworkflow.actions.length === 0 ? null : (
            <span>
              <div
                style={{
                  marginTop: "20px",
                  marginBottom: "7px",
                  display: "flex",
                }}
              >
                <div style={{ flex: "10" }}>
                  <b>Select the Startnode</b>
                </div>
              </div>

              <Autocomplete
                id="subflow_node_search"
                autoHighlight
                value={subworkflowStartnode}
                classes={{ inputRoot: classes.inputRoot }}
                ListboxProps={{
                  style: {
                    backgroundColor: theme.palette.inputColor,
                    color: theme.palette.text.primary,
                  },
                }}
                sx={{
                  '& .MuiOutlinedInput-root': {
                    height: 40, // Adjust the input height
                  },
                  '& .MuiAutocomplete-input': {
                    padding: '8px', // Adjust the text padding
                  },
                }}
                getOptionSelected={(option, value) => option.id === value.id}
                getOptionLabel={(option) => {
                  if (option === undefined || option === null || option.label === undefined || option.label === null) {
                    if (option.length === 36) {

                    }

                    return "Default";
                  }

                  const newname = (
                    option.label.charAt(0).toUpperCase() + option.label.substring(1)
                  ).replaceAll("_", " ");
                  return newname;
                }}
                options={subworkflow.actions}
                fullWidth
                style={{
                  backgroundColor: theme.palette.inputColor,
                  borderRadius: theme.palette?.borderRadius,
                }}
                onChange={(event, newValue) => {
                  setLastSaved(false)
                  handleSubflowStartnodeSelection({ target: { value: newValue } })
                }}
                renderOption={(props, action, state) => {
                  const isParent = getParents(selectedTrigger).find(
                    (parent) => parent.id === action.id
                  )

                  return (
                    <MenuItem
                      onMouseOver={() => {
                        if (subworkflow.id === workflow.id) {
                          handleActionHover(true, action.id)
                        }
                      }}
                      onMouseOut={() => {
                        if (subworkflow.id === workflow.id) {
                          handleActionHover(false, action.id)
                        }
                      }}
                      disabled={isCloud && isParent}
                      onClick={() => {
                        handleSubflowStartnodeSelection({
                          target: {
                            value: action
                          }
                        })
                        document.activeElement.blur()
                      }}
                      style={{
                        backgroundColor: theme.palette.inputColor,
                        color: isParent ? "red" : theme.palette.text.primary,
                      }}
                      value={action}
                    >
                      {action.label}
                    </MenuItem>
                  );
                }}
                renderInput={(params) => {
                  return (
                    <TextField
                      style={theme.palette.textFieldStyle}
                      {...params}
                      label="Select a start-node (optional)"
                      variant="outlined"
                    />
                  );
                }}
              />
            </span>
          )}
          <div
            style={{
              marginTop: "20px",
              marginBottom: "5px",
              display: "flex",
              width: "100%",
            }}
          >
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
              <b>Runtime Argument</b>

				{/*parentParamValue !== undefined && parentParamValue !== null && parentParamValue !== "" && parentParamValue !== data.value ?
					<Tooltip title={`Parent value is different. Click to reset.`} placement="top" arrow>
						<RestoreIcon 
							style={{
								marginRight: 10, 
								marginBottom: 5, 
								cursor: "pointer", 
								color: theme.palette.distributionColor,
							}}
							onClick={() => {
								selectedActionParameters[count].value = parentParamValue
								selectedAction.parameters[count].value = parentParamValue
								setSelectedAction(selectedAction)
								setUpdate(Math.random())
							}}
						/>
					</Tooltip>
				: null*/}

              <Tooltip title="Expand editor window" placement="top">
                <IconButton
                  onClick={(event) => {
                    event.preventDefault()
                    setCodeEditorModalOpen(true)
                    setActiveDialog("codeeditor")
                    var parsedvalue = workflow?.triggers[selectedTriggerIndex]?.parameters[1]?.value

                    // Right now Handling subflow only with this
                    navigate(`?trigger_id=${selectedTrigger.id}&trigger_field=${"argument"}&trigger_name=${selectedTrigger.label}`)
                    setEditorData({
                      "name": workflow.triggers[selectedTriggerIndex].parameters[1].name,
                      "value": parsedvalue,
                      "field_number": 1,
                      "actionlist": triggerActionList,
                      "field_id": "subflow_exec_field",
                    })
                  }}
                >
                  <OpenInFullIcon
                    style={{
                      color: theme.palette.textColor,
                      height: 20,
                      width: 20
                    }}
                  />
                </IconButton>
              </Tooltip>
            </div>
          </div>
          <TextField
            id="subflow_exec_field"
            style={{
              backgroundColor: theme.palette.textFieldStyle.backgroundColor,
              color: theme.palette.textFieldStyle.color,
            }}
            InputProps={{
              style: {
                backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                color: theme.palette.textFieldStyle.color,
              },
              endAdornment: (
                <InputAdornment position="end">
                  <Tooltip title="Autocomplete text" placement="top">
                    <AddCircleOutlineIcon
                      style={{ cursor: "pointer" }}
                      onClick={(event) => {
                        setMenuPosition({
                          top: event.pageY + 10,
                          left: event.pageX + 10,
                        });
                        //setShowDropdownNumber(3)
                        setShowDropdown(true);
                      }}
                    />
                  </Tooltip>
                </InputAdornment>
              ),
            }}
            rows="6"
            multiline
            fullWidth
            color="primary"
            placeholder="Some execution data"
            value={selectedTriggerValue || ""}
            onChange={(e) => {  
              setLastSaved(false)
              setSelectedTriggerValue(e.target.value)
            }}
            onBlur={(e) => {
              workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value
              setWorkflow(workflow)
            }}
          />
          {!showDropdown ? null :
            <Menu
              anchorReference="anchorPosition"
              anchorPosition={menuPosition}
              onClose={() => {
                handleMenuClose();
              }}
              open={!!menuPosition}
              style={{
                border: `2px solid #FF8544`,
                color: theme.palette.text.primary,
                marginTop: 2,
              }}
            >
              {triggerActionList.map((innerdata) => {
                const icon =
                  innerdata.type === "action" ? (
                    <AppsIcon style={{ marginRight: 10 }} />
                  ) : innerdata.type === "workflow_variable" ||
                    innerdata.type === "execution_variable" ? (
                    <FavoriteBorderIcon style={{ marginRight: 10 }} />
                  ) : innerdata.type === "Shuffle DB" ? 
                  <StorageIcon style={{ marginRight: 10,  }} />
                :
                  <ScheduleIcon style={{ marginRight: 10 }} />

                const handleExecArgumentHover = (inside) => {
                  var exec_text_field = document.getElementById(
                    "execution_argument_input_field"
                  );
                  if (exec_text_field !== null) {
                    if (inside) {
                      exec_text_field.style.border = "2px solid #FF8544";
                    } else {
                      exec_text_field.style.border = "";
                    }
                  }

                  // Also doing arguments
                  if (
                    workflow.triggers !== undefined &&
                    workflow.triggers !== null &&
                    workflow.triggers.length > 0
                  ) {
                    for (let triggerkey in workflow.triggers) {
                      const item = workflow.triggers[triggerkey];

                      if (cy !== undefined && cy !== null) {
                        var node = cy.getElementById(item.id);
                        if (node.length > 0) {
                          if (inside) {
                            node.addClass("shuffle-hover-highlight");
                          } else {
                            node.removeClass("shuffle-hover-highlight");
                          }
                        }
                      }
                    }
                  }
                }

                const handleActionHover = (inside, actionId) => {
                  if (cy !== undefined && cy !== null) {
                    var node = cy.getElementById(actionId);
                    if (node.length > 0) {
                      if (inside) {
                        node.addClass("shuffle-hover-highlight");
                      } else {
                        node.removeClass("shuffle-hover-highlight");
                      }
                    }
                  }
                };

                const handleMouseover = () => {
                  if (innerdata.type === "Runtime Argument") {
                    handleExecArgumentHover(true);
                  } else if (innerdata.type === "action") {
                    handleActionHover(true, innerdata.id);
                  }
                };

                const handleMouseOut = () => {
                  if (innerdata.type === "Runtime Argument") {
                    handleExecArgumentHover(false);
                  } else if (innerdata.type === "action") {
                    handleActionHover(false, innerdata.id);
                  }
                };

                var parsedPaths = [];

                if (innerdata.type === "workflow_variable") {
                  // Try to parse the value if it's a string that could be JSON
                  if (typeof innerdata.value === "string") {
                    try {
                      const parsedValue = JSON.parse(innerdata.value)
                      if (typeof parsedValue === "object") {
                        parsedPaths = GetParsedPaths(parsedValue, "");
                      }
                    } catch (e) {
                      // Not valid JSON, use the value directly
                      parsedPaths = GetParsedPaths(innerdata.value, "");
                    }
                  } else if (typeof innerdata.value === "object") {
                    parsedPaths = GetParsedPaths(innerdata.value, "");
                  }
                } else if (typeof innerdata.example === "object") {
                  parsedPaths = GetParsedPaths(innerdata.example, "");
                }

                const coverColor = "#82ccc3"

                return parsedPaths.length > 0 ? (
                  <span>
                    {/*
                    <NestedMenuItem
                      key={innerdata.name}
                      label={
                        <div style={{ display: "flex" }}>
                          {icon} {innerdata.name}
                        </div>
                      }
                      parentMenuOpen={!!menuPosition}
                      style={{
                        backgroundColor: theme.palette.inputColor,
                        color: theme.palette.text.primary,
                        minWidth: 250,
                      }}
                      onClick={() => {
                        handleItemClick([innerdata]);
                      }}
                    >
                      {parsedPaths.map((pathdata, index) => {
                        // FIXME: Should be recursive in here
                        const icon =
                          pathdata.type === "value" ? (
                            <VpnKeyIcon style={iconStyle} />
                          ) : pathdata.type === "list" ? (
                            <FormatListNumberedIcon style={iconStyle} />
                          ) : (
                            <ExpandMoreIcon style={iconStyle} />
                          )
      
                        return (
                          <MenuItem
                            key={pathdata.name}
                            style={{
                              backgroundColor: theme.palette.inputColor,
                              color: theme.palette.text.primary,
                              minWidth: 250,
                            }}
                            value={pathdata}
                            onMouseOver={() => { }}
                            onClick={() => {
                              handleItemClick([innerdata, pathdata]);
                            }}
                          >
                            <Tooltip
                              color="primary"
                              title={`Ex. value: ${pathdata.value}`}
                              placement="left"
                            >
                              <div style={{ display: "flex" }}>
                                {icon} {pathdata.name}
                              </div>
                            </Tooltip>
                          </MenuItem>
                        );
                      })}
                    </NestedMenuItem>
                    */}

                    <NestedMenuItem
                      key={innerdata.name}
                      label={
                        <div style={{ display: "flex", marginLeft: 0, }}>
                          {icon} {innerdata.name}
                        </div>
                      }
                      parentMenuOpen={!!menuPosition}
                      style={{
                        color: theme.palette.text.primary,
                        minWidth: 250,
                        maxWidth: 250,
                        maxHeight: 50,
                        overflow: "hidden",
                      }}
                      onClick={() => {
                        console.log(innerdata.example)
                        handleItemClick([innerdata]);
                      }}
                    >
                      <Paper style={{ minHeight: 500, maxHeight: 500, minWidth: 275, maxWidth: 275, position: "fixed", top: menuPosition?.top - 200, left: menuPosition?.left - 455, padding: "10px 0px 10px 10px", overflow: "hidden", overflowY: "auto", border: "1px solid rgba(255,255,255,0.3)", }}>

                        <MenuItem
                          key={innerdata.name}
                          style={{
                            // backgroundColor: theme.palette.inputColor,
                            marginLeft: 15,
                            color: theme.palette.text.primary,
                            minWidth: 250,
                            maxWidth: 250,
                            padding: 0,
                            position: "relative",
                          }}
                          value={innerdata}
                          onMouseOver={() => {
                            //console.log("HOVER: ", pathdata);
                          }}
                          onClick={() => {
                            handleItemClick([innerdata]);
                          }}
                        >
                          <Typography variant="h6" style={{ paddingBottom: 5 }}>
                            {innerdata.name}
                          </Typography>
                        </MenuItem>

                        {parsedPaths.map((pathdata, index) => {
                          // FIXME: Should be recursive in here
                          //<VpnKeyIcon style={iconStyle} />
                          const icon =
                            pathdata.type === "value" ? (
                              <span style={{ marginLeft: 9, }} />
                            ) : pathdata.type === "list" ? (
                              <FormatListNumberedIcon style={{ marginLeft: 9, marginRight: 10, }} />
                            ) : (
                              <CircleIcon style={{ marginLeft: 9, marginRight: 10, color: coverColor }} />
                            );
                          //<ExpandMoreIcon style={iconStyle} />

                          const indentation_count = (pathdata.name.match(/\./g) || []).length + 1
                          const baseIndent = <div style={{ marginLeft: 20, height: 30, width: 1, backgroundColor: coverColor, }} />
                          //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0
                          const boxPadding = 0
                          const namesplit = pathdata.name.split(".")
                          const newname = namesplit[namesplit.length - 1]
                          return (
                            <MenuItem
                              key={pathdata.name}
                              style={{
                                // backgroundColor: theme.palette.inputColor,
                                color: theme.palette.text.primary,
                                minWidth: 250,
                                maxWidth: 250,
                                padding: boxPadding,
                              }}
                              value={pathdata}
                              onMouseOver={() => {
                                //console.log("HOVER: ", pathdata);
                              }}
                              onClick={() => {
                                handleItemClick([innerdata, pathdata]);
                              }}
                            >
                              <Tooltip
                                color="primary"
                                title={`Ex. value: ${pathdata.value}`}
                                placement="left"
                              >
                                <div style={{ display: "flex", height: 30, }}>
                                  {Array(indentation_count).fill().map((subdata, subindex) => {
                                    return (
                                      baseIndent
                                    )
                                  })}
                                  {icon} {newname}
                                  {pathdata.type === "list" ? <SquareFootIcon style={{ marginleft: 10, }} onClick={(e) => {

                                  }} /> : null}
                                </div>
                              </Tooltip>
                            </MenuItem>
                          );
                        })}
                      </Paper>
                    </NestedMenuItem>
                  </span>
                ) : (
                  <MenuItem
                    key={innerdata.name}
                    style={{
                      // backgroundColor: theme.palette.inputColor,
                      color: theme.palette.text.primary,
                    }}
                    value={innerdata}
                    onMouseOver={() => handleMouseover()}
                    onMouseOut={() => {
                      handleMouseOut();
                    }}
                    onClick={() => {
                      handleItemClick([innerdata]);
                    }}
                  >
                    <Tooltip
                      color="primary"
                      title={`Value: ${innerdata.value}`}
                      placement="left"
                    >
                      <div style={{ display: "flex" }}>
                        {icon} {innerdata.name}
                      </div>
                    </Tooltip>
                  </MenuItem>
                );
              })}
            </Menu>
          }
          {/*
								<div
									style={{
										marginTop: "20px",
										marginBottom: "7px",
										display: "flex",
									}}
								>
									<div style={{ flex: "10" }}>
										<b>API-key </b>
									</div>
								</div>
								<TextField
									style={{
										backgroundColor: theme.palette.inputColor,
										borderRadius: theme.palette?.borderRadius,
									}}
									InputProps={{
										style: {
										},
									}}
									fullWidth
									color="primary"
									placeholder="Your apikey"
									defaultValue={
										workflow.triggers[selectedTriggerIndex].parameters[2].value
									}
									onBlur={(e) => {
										workflow.triggers[selectedTriggerIndex].parameters[2].value =
											e.target.value;
										setWorkflow(workflow);
									}}
								/>
							*/}
        </div>
      </div>

      {/*
          <div>
            <div>
            <div className="app">
              <div style={{ display: "flex", marginTop: 50 }}>
                <div style={{ flex: "10" }}>
                  <b>Authentication Override</b>
                </div>
              </div>

              <div style={{ display: "flex", marginTop: 10 }}>
                <div style={{ flex: "10", marginLeft: 10 }}>
                  <AppAuthSelector appAuthData={appAuthentication} />
                </div>
              </div>
            </div>
            </div>
          </div>
		  */}
    </div>


  const CommentSidebar = () => {
    if (Object.getOwnPropertyNames(selectedComment).length > 0) {

      return (
        <div style={appApiViewStyle}>
          <h3 style={{ marginBottom: "5px" }}>Comment</h3>
          <a
            rel="noopener noreferrer"
            target="_blank"
            href="https://shuffler.io/docs/workflows#comments"
            style={{ textDecoration: "none", color: theme.palette.linkColor }}
          >
            What are comments?
          </a>
          <Divider
            style={{
              marginBottom: 10,
              marginTop: 10,
              height: 1,
              width: "100%",
              backgroundColor: "rgb(91, 96, 100)",
            }}
          />
          <div>Name</div>
          <TextField
            style={{
              backgroundColor: theme.palette.textFieldStyle.backgroundColor,
              color: theme.palette.textFieldStyle.color,
              borderRadius: theme.palette?.textFieldStyle.borderRadius,
            }}
            InputProps={{
              style: {
                backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                color: theme.palette.textFieldStyle.color,
              },
            }}
            multiline
            rows="4"
            fullWidth
            color="primary"
            defaultValue={selectedComment.label}
            placeholder="Comment"
            onChange={(event) => {
              selectedComment.label = event.target.value;
              setSelectedComment(selectedComment);
            }}
          />
          <div style={{ display: "flex", marginTop: 10 }}>
            <div style={{
              width: "50%"
            }}>
              <div>Justify</div>
              <Select
                style={{ backgroundColor: theme.palette.inputColor }}
                fullWidth
                defaultValue="center"
                value={selectedComment.textHalign !== "center" && selectedComment.textHalign !== undefined ? selectedComment.textHalign === "left" ? "right" : "left" : "center"}
                onChange={(event) => {
                  const alignment = event.target.value;
                  const width = selectedComment.width || 250; // Default width if undefined

                  // Compute new values
                  let textHalign = alignment !== "center" ? alignment === "left" ? "right" : "left" : "center";
                  let textMarginX = "0px"; // Default for center alignment

                  if (alignment === "left") {
                    textMarginX = `-${width}px`;
                  } else if (alignment === "right") {
                    textMarginX = `${width}px`;
                  }
                  selectedComment.textHalign = textHalign;
                  selectedComment.textMarginX = textMarginX;
                  // Update state properly
                  setSelectedComment((prev) => ({
                    ...prev,
                    textHalign,
                    textMarginX,
                  }));

                  // Use useEffect or separate logging to confirm state changes
                }}
              >
                <MenuItem value="left">Left</MenuItem>
                <MenuItem value="center">Center</MenuItem>
                <MenuItem value="right">Right</MenuItem>
              </Select>
            </div>
            <div style={{ marginLeft: 5, width: "50%" }}>
              <div>Align</div>
              <Select
                fullWidth
                style={{ backgroundColor: theme.palette.inputColor }}
                defaultValue="center"
                value={selectedComment.textValign !== "center" && selectedComment.textHalign !== undefined ? selectedComment.textValign === "top" ? "bottom" : "top" : "center" || "center"}
                onChange={(event) => {
                  const height = selectedComment.height || 150; // Default width if undefined

                  let textValign = event.target.value === "center" ? "center" : event.target.value === "top" ? "bottom" : "top";
                  let textMarginY = "0px"; // Default for center alignment

                  if (textValign === "top") {
                    textMarginY = `${height}px`;
                  } else if (textValign === "bottom") {
                    textMarginY = `-${height}px`;
                  }
                  selectedComment.textValign = textValign;
                  selectedComment.textMarginY = textMarginY;
                  setSelectedComment((prev) => ({
                    ...prev,
                    textValign,
                    textMarginY
                  }));
                }}
              >
                <MenuItem value="top">Top</MenuItem>
                <MenuItem value="center">Center</MenuItem>
                <MenuItem value="bottom">Bottom</MenuItem>
              </Select>
            </div>
          </div>
          <div style={{ display: "flex", marginTop: 10 }}>
            <div>
              <div>Height</div>
              <TextField
                style={{
                  backgroundColor: theme.palette.inputColor,
                  borderRadius: theme.palette?.borderRadius,
                }}
                InputProps={{
                  style: {
                  },
                }}
                fullWidth
                color="primary"
                placeholder={"150"}
                defaultValue={selectedComment.height}
                onChange={(event) => {
                  selectedComment.height = event.target.value;
                  setSelectedComment(selectedComment);
                }}
              />
            </div>
            <div style={{ marginLeft: 5 }}>
              <div>Width</div>
              <TextField
                style={{
                  backgroundColor: theme.palette.inputColor,
                  borderRadius: theme.palette?.borderRadius,
                }}
                InputProps={{
                  style: {
                  },
                }}
                fullWidth
                color="primary"
                placeholder={"200"}
                defaultValue={selectedComment.width}
                onChange={(event) => {
                  selectedComment.width = event.target.value;
                  setSelectedComment(selectedComment);
                }}
              />
            </div>
          </div>
          <div style={{ display: "flex", marginTop: 10 }}>
            <div>
              <div>Background</div>
              <TextField
                style={{
                  backgroundColor: theme.palette.inputColor,
                  borderRadius: theme.palette?.borderRadius,
                }}
                InputProps={{
                  style: {
                  },
                }}
                fullWidth
                color="primary"
                placeholder={"#1f2023"}
                defaultValue={selectedComment["backgroundcolor"]}
                onChange={(event) => {
                  selectedComment.backgroundcolor = event.target.value;
                  setSelectedComment(selectedComment);
                }}
              />
            </div>
            <div style={{ marginLeft: 5 }}>
              <div>Text Color</div>
              <TextField
                style={{
                  backgroundColor: theme.palette.inputColor,
                  borderRadius: theme.palette?.borderRadius,
                }}
                InputProps={{
                  style: {
                  },
                }}
                fullWidth
                color="primary"
                placeholder={"#ffffff"}
                defaultValue={selectedComment.color}
                onChange={(event) => {
                  selectedComment.color = event.target.value;
                  setSelectedComment(selectedComment);
                }}
              />
            </div>
          </div>
          <div style={{ marginTop: 15, }}>Background-Image</div>
          <TextField
            style={{
              backgroundColor: theme.palette.inputColor,
              borderRadius: theme.palette?.borderRadius,
            }}
            InputProps={{
              style: {
              },
            }}
            fullWidth
            color="primary"
            placeholder={"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSr69kkDcJiR4Vm59ypxhGkD1JDIV0oLVDiBQ&usqp=CAU"}
            defaultValue={selectedComment["backgroundimage"]}
            onChange={(event) => {
              selectedComment.backgroundimage = event.target.value;
              setSelectedComment(selectedComment);
            }}
          />
        </div>
      );
    }

    return null;
  };

  // Special SCHEDULE handler
  var trigger_header_auth = ""
  if (Object.getOwnPropertyNames(selectedTrigger)?.length > 0 && workflow?.triggers !== null && workflow?.triggers !== undefined && workflow?.triggers?.length >= selectedTriggerIndex && workflow?.triggers[selectedTriggerIndex] !== undefined) {
    if (selectedTrigger.trigger_type === "WEBHOOK") {
      if (workflow.triggers[selectedTriggerIndex] === undefined) {
        return null;
      }

      if (
        workflow.triggers[selectedTriggerIndex].parameters === undefined ||
        workflow.triggers[selectedTriggerIndex].parameters === null ||
        workflow.triggers[selectedTriggerIndex].parameters.length === 0
      ) {
        workflow.triggers[selectedTriggerIndex].parameters = [];
        workflow.triggers[selectedTriggerIndex].parameters[0] = {
          name: "url",
          value: referenceUrl + "webhook_" + selectedTrigger.id,
        };
        workflow.triggers[selectedTriggerIndex].parameters[1] = {
          name: "tmp",
          value: "webhook_" + selectedTrigger.id,
        };
        workflow.triggers[selectedTriggerIndex].parameters[2] = {
          name: "auth_headers",
          value: "",
        };
        workflow.triggers[selectedTriggerIndex].parameters[3] = {
          name: "custom_response_body",
          value: "",
        };
        workflow.triggers[selectedTriggerIndex].parameters[4] = {
          name: "await_response",
          value: "v1",
        };
        setWorkflow(workflow);
      } else {
        // Always update
        const newUrl = referenceUrl + "webhook_" + selectedTrigger.id;
        //console.log("Validating webhook url: ", newUrl);
        if (selectedTrigger.environment !== "cloud") {
          if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
            console.log("Url is wrong. NOT updating because of hybrid.");
            //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl;
            //setWorkflow(workflow);
          }
        }
      }

      trigger_header_auth =
        workflow.triggers[selectedTriggerIndex].parameters.length > 2
          ? workflow.triggers[selectedTriggerIndex].parameters[2].value
          : "";
    }
  }

  const WebhookSidebar = !selectedTrigger || Object.getOwnPropertyNames(selectedTrigger)?.length === 0 || !workflow?.triggers || workflow?.triggers[selectedTriggerIndex] === undefined || selectedTrigger?.trigger_type !== "WEBHOOK" ? null :
    <div style={{...appApiViewStyle, overflow: 'hidden'}}>
      <h3 style={{ marginBottom: "5px" }}>
        {selectedTrigger.app_name}: {selectedTrigger.status}
      </h3>
      <a
        rel="noopener noreferrer"
        target="_blank"
        href="https://shuffler.io/docs/triggers#webhook"
        style={{ textDecoration: "none", color: theme.palette.linkColor }}
      >
        What are webhooks?
      </a>
      <Divider
        style={{
          marginBottom: "10px",
          marginTop: "10px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div>Name</div>
      <TextField
        style={theme.palette.textFieldStyle}
        InputProps={{
          style: theme.palette.innerTextfieldStyle,
        }}
        fullWidth
        color="primary"
        placeholder={selectedTrigger.label}
        defaultValue={selectedTrigger?.label}
        onChange={selectedTriggerChange}
		disabled={selectedTrigger?.parent_controlled === true && workflow?.parentorg_workflow?.length > 0}
      />
      {apps !== undefined && apps !== null && apps.length > 0 ?
        <div style={{ marginTop: 35, }}>
          <Autocomplete
            id="action_search"
            autoHighlight
            value={selectedTrigger.app_association}
            classes={{ inputRoot: classes.inputRoot }}
            ListboxProps={{
              style: {
                backgroundColor: theme.palette.inputColor,
                color: theme.palette.text.primary,
              },
            }}
            sx={{
              '& .MuiOutlinedInput-root': {
                height: 40, // Adjust the input height
              },
              '& .MuiAutocomplete-input': {
                padding: '8px', // Adjust the text padding
              },
            }}
            style={{
              backgroundColor: theme.palette.inputColor,
              borderRadius: theme.palette?.borderRadius,
            }}
            filterOptions={(options, { inputValue }) => {
              const lowercaseValue = inputValue.toLowerCase()
              options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue))

              return options
            }}
            getOptionLabel={(option) => {
              if (
                option === undefined ||
                option === null ||
                option.name === undefined ||
                option.name === null
              ) {
                return null;
              }

              const newname = (
                option.name.charAt(0).toUpperCase() + option.name.substring(1)
              ).replaceAll("_", " ");

              return newname;
            }}
            options={sortByKey(apps, "name")}
            fullWidth
            onChange={(event, newValue) => {
              // Workaround with event lol
              if (newValue !== undefined && newValue !== null) {
                var parsedvalue = JSON.parse(JSON.stringify(newValue))
                parsedvalue.actions = []
                parsedvalue.authentication = {}
                selectedTrigger.app_association = parsedvalue
                setUpdate(Math.random());
              }
            }}
            renderOption={(props, app, state) => {
              var appname = app.name.replaceAll("_", " ")
              appname = appname.charAt(0).toUpperCase() + appname.substring(1)

              return (
                <Tooltip
                  color="secondary"
                  title={appname}
                  placement="left"
                >
                  <MenuItem
                    onClick={() => {
                      const newValue = app

                      if (newValue !== undefined && newValue !== null) {
                        var parsedvalue = JSON.parse(JSON.stringify(newValue))
                        parsedvalue.actions = []
                        parsedvalue.authentication = {}
                        selectedTrigger.app_association = parsedvalue
                        selectedTrigger.large_image = app.large_image

                        if (cy !== undefined && cy !== null) {
                          const foundnode = cy.getElementById(selectedTrigger.id)
                          if (foundnode !== undefined && foundnode !== null) {
                            foundnode.data("large_image", app.large_image)
                          }
                        }

                        setUpdate(Math.random());
                      }
                      document.activeElement.blur();
                    }}
                  >
                    <div style={{ display: "flex", marginBottom: 0, }}>
                      <Avatar variant="rounded">
                        <img
                          alt={appname}
                          src={app.large_image}
                          style={{ width: 50, height: 50, }}
                        />
                      </Avatar>
                      <Typography variant="body1" style={{ marginLeft: 15, }}>
                        {appname}
                      </Typography>
                    </div>
                  </MenuItem>
                </Tooltip>
              )
            }}
            renderInput={(params) => {
              return (
                <TextField
                  style={theme.palette.textFieldStyle}
                  {...params}
                  label="Associated App (optional)"
                  variant="outlined"
                />
              );
            }}
          />

        </div>
        : null}

      {selectedTrigger.status === "running" || triggerEnvironments?.length < 2 ? null :
        <div style={{ marginTop: 20 }}>
          <Typography>Environment</Typography>
          <Select
            MenuProps={{
              disableScrollLock: true,
            }}
            value={selectedTrigger.environment === undefined || selectedTrigger.environment === null || selectedTrigger.environment === "" ?
              environments?.find((env) => {
                if (env.archived) {
                  return false
                }

                if (env.name === "cloud" || env.type === "cloud") {
                  return false
                }


                return true
              }).name
              : selectedTrigger.environment}
            disabled={selectedTrigger.status === "running"}
            SelectDisplayProps={{
              style: {
              },
            }}
            fullWidth
            onChange={(e) => {
              selectedTrigger.environment = e.target.value;
              if (e.target.value === "cloud") {
                const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
                const urlpath = tmpvalue.slice(3, tmpvalue.length);
                const newurl = "https://shuffler.io/" + urlpath.join("/");
                workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
              } else {
                const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
                const urlpath = tmpvalue.slice(3, tmpvalue.length);
                const newurl = window.location.origin + "/" + urlpath.join("/");
                workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
              }

              console.log("New value: ", workflow.triggers[selectedTriggerIndex].parameters[0])
              selectedTrigger.parameters[0] = workflow.triggers[selectedTriggerIndex].parameters[0]
              setSelectedTrigger(selectedTrigger);
              setWorkflow(workflow);
              setUpdate(Math.random());
            }}
            style={{
              backgroundColor: theme.palette.inputColor,
              color: theme.palette.text.primary,
              height: 50,
            }}
          >
            {triggerEnvironments.map((data) => {
              if (data.archived) {
                return null
              }

              return (
                <MenuItem
                  key={data}
                  style={{ backgroundColor: theme.palette.inputColor, color: theme.palette.text.primary }}
                  value={data}
                >
                  {data}
                </MenuItem>
              );
            })}
          </Select>
        </div>
      }
      <Divider
        style={{
          marginTop: "20px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div style={{ flex: "6", marginTop: "20px" }}>
        <div>
          <b>Parameters</b>
          <div
            style={{
              marginTop: "20px",
              marginBottom: "7px",
              display: "flex",
            }}
          >
            <div
              style={{
                width: "17px",
                height: "17px",
                borderRadius: 17 / 2,
                backgroundColor: "#FF8544",
                marginRight: "10px",
              }}
            />
            <div style={{ flex: "10" }}>
              <b>Webhook URI </b>
            </div>
          </div>
          <TextField
            style={{
              backgroundColor: theme.palette.textFieldStyle.backgroundColor,
              borderRadius: theme.palette.textFieldStyle.borderRadius,
            }}
            id="webhook_uri_field"
            onClick={() => {
            }}
            helperText={
              workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined &&
                workflow.triggers[selectedTriggerIndex].parameters[0].value !== null &&
                (workflow.triggers[
                  selectedTriggerIndex
                ].parameters[0].value.includes("localhost") ||
                  workflow.triggers[
                    selectedTriggerIndex
                  ].parameters[0].value.includes("127.0.0.1")) ? (

                <span
                  style={{ color: theme.palette.text.primary, marginBottom: 5, marginleft: 5 }}
                >
                  PS: This does NOT work with localhost. Use your local IP
                  instead.
                </span>
              ) : null
            }
            InputProps={{
              style: {
              },
              endAdornment:
                <InputAdornment position="end">
                  <IconButton
                    aria-label="Copy webhook"
                    onClick={() => {
                      var copyText = document.getElementById("webhook_uri_field");
                      if (copyText !== undefined && copyText !== null) {
                        const clipboard = navigator.clipboard;
                        if (clipboard === undefined) {
                          toast("Can only copy over HTTPS (port 3443)");
                          return;
                        }

                        navigator.clipboard.writeText(copyText.value);
                        copyText.select();
                        copyText.setSelectionRange(
                          0,
                          99999
                        ); /* For mobile devices */

                        /* Copy the text inside the text field */
                        document.execCommand("copy");
                        toast("Copied Webhook URL");
                      } else {
                        console.log("Couldn't find webhook URI field: ", copyText);
                      }
                    }}
                    edge="end"
                  >
                    <ContentCopyIcon />
                  </IconButton>
                </InputAdornment>
            }}
            fullWidth
            disabled
            value={
              workflow.triggers[selectedTriggerIndex].parameters[0].value
            }
            color="primary"
            placeholder="10"
            onBlur={(e) => {
              setTriggerCronWrapper(e.target.value);
            }}
          />
          <div
            style={{
              marginTop: "20px",
              marginBottom: "7px",
              display: "flex",
            }}
          >
            <Button
              variant="contained"
              style={{ flex: "1" }}
              disabled={selectedTrigger.status === "running"}
              onClick={() => {
                newWebhook(workflow.triggers[selectedTriggerIndex]);
              }}
              color="primary"
            >
              Start
            </Button>
            <Button
              variant="contained"
              style={{ flex: "1" }}
              disabled={selectedTrigger.status !== "running"}
              onClick={() => {
                deleteWebhook(selectedTrigger, selectedTriggerIndex);
              }}
              color="primary"
            >
              Stop
            </Button>
          </div>
          <Divider
            style={{
              marginTop: "20px",
              height: "1px",
              width: "100%",
              backgroundColor: "rgb(91, 96, 100)",
            }}
          />
          <div
            style={{
              marginTop: 25,
              marginBottom: "7px",
              display: "flex",
            }}
          >
            <div
              style={{
                width: "17px",
                height: "17px",
                borderRadius: 17 / 2,
                backgroundColor: yellow,
                marginRight: "10px",
              }}
            />
            <div style={{ flex: "10" }}>
              <b>Authentication headers</b>
            </div>
          </div>
          <div>
            <TextField
              style={{
                backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                borderRadius: theme.palette?.textFieldStyle.borderRadius,
              }}
              id="webhook_uri_header"
              onClick={() => { }}
              InputProps={{
                style: {
                  backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                  color: theme.palette.textFieldStyle.color,
                },
              }}
              fullWidth
              multiline
              rows="4"
              defaultValue={trigger_header_auth}
              color="primary"
              disabled={selectedTrigger.status === "running"}
              placeholder={"AUTH_HEADER=AUTH_VALUE1"}
              onBlur={(e) => {
                const value = e.target.value;
                if (selectedTrigger.parameters === null) {
                  selectedTrigger.parameters = [];
                }

                workflow.triggers[selectedTriggerIndex].parameters[2] = {
                  value: value,
                  name: "auth_headers",
                };
                setWorkflow(workflow);
              }}
            />
          </div>
          <div
            style={{
              marginTop: "20px",
              marginBottom: "7px",
              display: "flex",
            }}
          >
            <div
              style={{
                width: "17px",
                height: "17px",
                borderRadius: 17 / 2,
                backgroundColor: yellow,
                marginRight: "10px",
              }}
            />
            <div style={{ flex: "10" }}>
              <b>Custom Response</b>
            </div>
          </div>
          <div style={{ marginBottom: 20, }}>
            <TextField
              style={{
                backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                borderRadius: theme.palette?.textFieldStyle.borderRadius,
              }}
              id="webhook_uri_header"
              onClick={() => { }}
              InputProps={{
                style: {
                  backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                  color: theme.palette.textFieldStyle.color,
                },
              }}
              fullWidth
              multiline
              rows="2"
              color="primary"
              disabled={selectedTrigger.status === "running"}
              placeholder={"OK"}
              onBlur={(e) => {
                const value = e.target.value;
                if (selectedTrigger.parameters === null) {
                  selectedTrigger.parameters = [];
                }

                workflow.triggers[selectedTriggerIndex].parameters[3] = {
                  value: value,
                  name: "custom_response_body",
                };
                setWorkflow(workflow);
              }}
            />
          </div>
          {workflow.triggers[selectedTriggerIndex].parameters.length > 4 ?
            <FormGroup
              style={{ paddingLeft: 10, backgroundColor: theme.palette.inputColor, marginBottom: 50, }}
              row
            >
              <FormControlLabel
                control={
                  <Checkbox
                    checked={workflow.triggers[selectedTriggerIndex].parameters[4].value.includes("v2")}
                    disabled={selectedTrigger.status === "running"}
                    onChange={(e) => {
                      if (selectedTrigger.parameters === null) {
                        selectedTrigger.parameters = [];
                      }

                      // Sets the webhook to run as version 2.. kinda
                      var value = "v2"
                      if (workflow.triggers[selectedTriggerIndex].parameters[4].value.includes("v2")) {
                        value = "v1"
                      }

                      workflow.triggers[selectedTriggerIndex].parameters[4] = {
                        name: "await_response",
                        value: value
                      }

                      setWorkflow(workflow)
                      setUpdate(Math.random())
                    }}
                    color="primary"
                    value="await_response"
                  />
                }
                label={<div style={{ color: theme.palette.text.primary }}>Wait For Response</div>}
              />
            </FormGroup>
            : null}
        </div>
      </div>
    </div>

  const stopMailSub = (trigger, triggerindex) => {
    // DELETE
    if (trigger.id === undefined) {
      return;
    }

    toast("Stopping mail trigger");
    const requesttype = triggerAuthentication.type;
    fetch(
      `${globalUrl}/api/v1/workflows/${props.match.params.key}/${requesttype}/${trigger.id}`,
      {
        method: "DELETE",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
        },
        credentials: "include",
      }
    )
      .then((response) => {
        if (response.status !== 200) {
          throw new Error("Status not 200 for stream results :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success) {
          toast("Successfully stopped trigger");
          // Set the status
          workflow.triggers[triggerindex].status = "stopped";
          trigger.status = "stopped";
          setWorkflow(workflow);
          setSelectedTrigger(trigger);
          saveWorkflow(workflow);
        } else {
          toast("Failed stopping trigger: " + responseJson.reason);
        }
      })
      .catch((error) => {
        //toast(error.toString());
        console.log("Stop mailsub error: ", error.toString());
      });
  };

  const newWebhook = (trigger) => {
    const hookname = trigger.label;
    if (hookname.length === 0) {
      toast("Missing name");
      return;
    }

    if (trigger.id.length !== 36) {
      toast("Missing id");
      return;
    }

    // Check the node it's connected to
    var startNode = workflow.start;
    const branch = workflow.branches.find(
      (branch) => branch.source_id === trigger.id
    );
    if (branch === undefined && (workflow.start === undefined || workflow.start === null || workflow.start.length === 0)) {
      toast("No webhook node defined");
    }

    toast("Starting webhook");
    if (branch !== undefined) {
      startNode = branch.destination_id;
    }

    const param = trigger.parameters.find((param) => param.name === "auth_headers");
    var auth = "";
    if (param !== undefined && param !== null) {
      auth = param.value;
    }


    // Version: v2 = await response for 30 sec
    const await_resp = trigger.parameters.find((param) => param.name === "await_response");
    var version = "";
    if (await_resp !== undefined && await_resp !== null) {
      version = await_resp.value;
    }

    const customRespParam = trigger.parameters.find(
      (param) => param.name === "custom_response_body"
    )
    var custom_response = "";
    if (customRespParam !== undefined && customRespParam !== null) {
      custom_response = customRespParam.value;
    }

    const data = {
      name: hookname,
      type: "webhook",
      id: trigger.id,
      workflow: workflow.id,
      start: startNode,
      environment: trigger.environment,
      auth: auth,
      custom_response: custom_response,
      version: version,
      version_timeout: 15,
    }

	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      	headers["Org-Id"] = workflow.org_id 
	}

    fetch(globalUrl + "/api/v1/hooks/new", {
      method: "POST",
      headers: headers,
      body: JSON.stringify(data),
      credentials: "include",
    })
      .then((response) => response.json())
      .then((responseJson) => {
        if (responseJson.success) {
          // Set the status
          toast("Successfully started webhook");
          trigger.status = "running";
          setSelectedTrigger(trigger);
          workflow.triggers[selectedTriggerIndex].status = "running";
          setWorkflow(workflow);
          saveWorkflow(workflow);

  		  loadTriggers(workflow.org_id)
        } else {
          toast("Failed starting webhook: " + responseJson.reason);
        }
      })
      .catch((error) => {
        //console.log(error.toString());
        console.log("New webhook error: ", error.toString());
      });
  };

  const deleteWebhook = (trigger, triggerindex) => {
    if (trigger.id === undefined) {
      return;
    }

	// Unselect everything in cytoscape
	if (cy !== undefined && cy !== null) {
		cy.$(":selected").unselect()
	}

	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	  console.log("ORGID: ", workflow.org_id)
	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
      	headers["Org-Id"] = workflow.org_id 
	}

	const url = `${globalUrl}/api/v1/hooks/${trigger.id}/delete`
    fetch(url, {
      method: "DELETE",
      headers: headers,
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for stream results :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (!responseJson.success) {
          if (responseJson.reason !== undefined) {
              toast.error("Failed to stop webhook: " + responseJson.reason);
          } else {
			  //toast.error("Failed to stop webhook. Please try again, or contact support@shuffler.io to get it sorted.");
		  }
        } else {
          toast("Successfully stopped webhook");

  		  loadTriggers(workflow.org_id)
        }
        if (workflow.triggers[triggerindex] !== undefined) {
          workflow.triggers[triggerindex].status = "stopped";
        }

        trigger.status = "stopped"
        setSelectedTrigger(trigger)
        setWorkflow(workflow)
        saveWorkflow(workflow)
        setSelectedTrigger({})

      })
      .catch((error) => {
        //toast(error.toString());
        toast(
          "Delete webhook error. Contact support or check logs if this persists.",
        );
      });
  };


  // POST to /api/v1/workflows
  const createWorkflow = (workflow, trigger_index) => {
	var headers = {
		"Content-Type": "application/json",
		"Accept": "application/json",
	}

	if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
		headers["Org-Id"] = workflow.org_id
	}

    fetch(globalUrl + "/api/v1/workflows", {
      method: "POST",
      headers: headers,
      body: JSON.stringify(workflow),
      credentials: "include",
    })
      .then((response) => {
        if (response.status === 200) {
          getAvailableWorkflows(trigger_index)
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id.length > 0) {
          toast("Successfully created workflow");

          handleWorkflowSelectionUpdate({ target: { value: responseJson } }, true)
        }
      })
      .catch((error) => {
        console.log("Create workflow error: ", error.toString())
      })
  }

  const UserinputSidebar = !selectedTrigger || Object.getOwnPropertyNames(selectedTrigger)?.length === 0 || !workflow?.triggers || !workflow?.triggers[selectedTriggerIndex] || !selectedTrigger.trigger_type || selectedTrigger.trigger_type !== "USERINPUT" ? null :
    <div style={appApiViewStyle}>
      <h3 style={{ marginBottom: "5px" }}>
        {selectedTrigger.app_name}
      </h3>
      <a
        rel="noopener noreferrer"
        target="_blank"
        href="https://shuffler.io/docs/triggers#user_input"
        style={{ textDecoration: "none", color: theme.palette.linkColor }}
      >
        What is the user input trigger?
      </a>
      <Divider
        style={{
          marginBottom: "10px",
          marginTop: "10px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div>Name</div>
      <TextField
        style={{
          backgroundColor: theme.palette.textFieldStyle.backgroundColor,
          borderRadius: theme.palette?.textFieldStyle.borderRadius,
        }}
        InputProps={{
          style: {
            backgroundColor: theme.palette.textFieldStyle.backgroundColor,
            color: theme.palette.textFieldStyle.color,
          },
        }}
        fullWidth
        color="primary"
        placeholder={selectedTrigger.label}
        defaultValue={selectedTrigger?.label}
        onChange={selectedTriggerChange}
		disabled={selectedTrigger?.parent_controlled === true && workflow?.parentorg_workflow?.length > 0}
      />

      {/*<div style={{ marginTop: "20px" }}>
            Environment:
            <TextField
              style={{
                backgroundColor: theme.palette.inputColor,
                borderRadius: theme.palette?.borderRadius,
              }}
              InputProps={{
                style: {
                  color: theme.palette.text.primary,
                  marginLeft: "5px",
                  maxWidth: "95%",
                  height: 50,
                  fontSize: "1em",
                },
              }}
              required
              disabled
              fullWidth
              color="primary"
              value={selectedTrigger.environment}
            />
          </div>
					*/}
      <div style={{ flex: "6", marginTop: 10, }}>
      <div style={{ marginTop: "20px", marginBottom: "5px", display: "flex", width: "100%", flexDirection: "column" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
          <b>Information</b>
          <Tooltip title="Expand editor window" placement="top">
            <IconButton
              onClick={(event) => {
                event.preventDefault()
                setCodeEditorModalOpen(true)
                setActiveDialog("codeeditor")
                var parsedvalue = workflow?.triggers[selectedTriggerIndex]?.parameters[0]?.value

                navigate(`?trigger_id=${selectedTrigger.id}&trigger_field=${"alertinfo"}&trigger_name=${selectedTrigger.label}`)
                setEditorData({
                  "name": workflow.triggers[selectedTriggerIndex].parameters[0].name,
                  "value": parsedvalue,
                  "field_number": 0,
                  "actionlist": triggerActionList,
                  "field_id": "information_field",
                })
              }}
            >
              <OpenInFullIcon
                style={{
                  color: theme.palette.textColor,
                  height: 20,
                  width: 20
                }}
              />
            </IconButton>
          </Tooltip>
        </div>
        <Typography color="textSecondary" sx={{fontSize: "13px"}}>
            The information you want to show the user. Supports variables. Supports Markdown & HTML.
          </Typography>
        </div>
        <TextField
          id="userinput_info_field"
          style={{
            backgroundColor: theme.palette.textFieldStyle.backgroundColor,
            borderRadius: theme.palette?.textFieldStyle.borderRadius,
          }}
          InputProps={{
            style: {
              backgroundColor: theme.palette.textFieldStyle.backgroundColor,
              color: theme.palette.textFieldStyle.color,
            },
            endAdornment: (
              <InputAdornment position="end">
                <Tooltip title="Autocomplete text" placement="top">
                  <AddCircleOutlineIcon
                    style={{ cursor: "pointer" }}
                    onClick={(event) => {
                      setMenuPosition({
                        top: event.pageY + 10,
                        left: event.pageX + 10,
                      });
                      //setShowDropdownNumber(3)
                      setShowDropdown(true);
                    }}
                  />
                </Tooltip>
              </InputAdornment>
            ),
          }}
          fullWidth
          rows="4"
          multiline
          value={selectedTriggerValue || ""}
          color="primary"
          placeholder=""
          onChange={(e) => {  
            setLastSaved(false)
            setSelectedTriggerValue(e.target.value)
          }}
          onBlur={(e) => {
            setLastSaved(false)
            setTriggerTextInformationWrapper(e.target.value);
          }}
        />
        {!showDropdown ? null :
          <Menu
              anchorReference="anchorPosition"
              anchorPosition={menuPosition}
              onClose={() => {
                handleMenuClose();
              }}
              open={!!menuPosition}
              style={{
                border: `2px solid #FF8544`,
                color: theme.palette.text.primary,
                marginTop: 2,
              }}
            >
              {triggerActionList.map((innerdata) => {
                const icon =
                  innerdata.type === "action" ? (
                    <AppsIcon style={{ marginRight: 10 }} />
                  ) : innerdata.type === "workflow_variable" ||
                    innerdata.type === "execution_variable" ? (
                    <FavoriteBorderIcon style={{ marginRight: 10 }} />
                  ) : innerdata.type === "Shuffle DB" ? 
                  <StorageIcon style={{ marginRight: 10,  }} />
                :
                  <ScheduleIcon style={{ marginRight: 10 }} />

                const handleExecArgumentHover = (inside) => {
                  var exec_text_field = document.getElementById(
                    "execution_argument_input_field"
                  );
                  if (exec_text_field !== null) {
                    if (inside) {
                      exec_text_field.style.border = "2px solid #FF8544";
                    } else {
                      exec_text_field.style.border = "";
                    }
                  }

                  // Also doing arguments
                  if (
                    workflow.triggers !== undefined &&
                    workflow.triggers !== null &&
                    workflow.triggers.length > 0
                  ) {
                    for (let triggerkey in workflow.triggers) {
                      const item = workflow.triggers[triggerkey];

                      if (cy !== undefined && cy !== null) {
                        var node = cy.getElementById(item.id);
                        if (node.length > 0) {
                          if (inside) {
                            node.addClass("shuffle-hover-highlight");
                          } else {
                            node.removeClass("shuffle-hover-highlight");
                          }
                        }
                      }
                    }
                  }
                }

                const handleActionHover = (inside, actionId) => {
                  if (cy !== undefined && cy !== null) {
                    var node = cy.getElementById(actionId);
                    if (node.length > 0) {
                      if (inside) {
                        node.addClass("shuffle-hover-highlight");
                      } else {
                        node.removeClass("shuffle-hover-highlight");
                      }
                    }
                  }
                };

                const handleMouseover = () => {
                  if (innerdata.type === "Runtime Argument") {
                    handleExecArgumentHover(true);
                  } else if (innerdata.type === "action") {
                    handleActionHover(true, innerdata.id);
                  }
                };

                const handleMouseOut = () => {
                  if (innerdata.type === "Runtime Argument") {
                    handleExecArgumentHover(false);
                  } else if (innerdata.type === "action") {
                    handleActionHover(false, innerdata.id);
                  }
                };

                var parsedPaths = [];

                if (innerdata.type === "workflow_variable") {
                  // Try to parse the value if it's a string that could be JSON
                  if (typeof innerdata.value === "string") {
                    try {
                      const parsedValue = JSON.parse(innerdata.value)
                      if (typeof parsedValue === "object") {
                        parsedPaths = GetParsedPaths(parsedValue, "");
                      }
                    } catch (e) {
                      // Not valid JSON, use the value directly
                      parsedPaths = GetParsedPaths(innerdata.value, "");
                    }
                  } else if (typeof innerdata.value === "object") {
                    parsedPaths = GetParsedPaths(innerdata.value, "");
                  }
                } else if (typeof innerdata.example === "object") {
                  parsedPaths = GetParsedPaths(innerdata.example, "");
                }

                const coverColor = "#82ccc3"

                return parsedPaths.length > 0 ? (
                  <span>
                    {/*
                    <NestedMenuItem
                      key={innerdata.name}
                      label={
                        <div style={{ display: "flex" }}>
                          {icon} {innerdata.name}
                        </div>
                      }
                      parentMenuOpen={!!menuPosition}
                      style={{
                        backgroundColor: theme.palette.inputColor,
                        color: theme.palette.text.primary,
                        minWidth: 250,
                      }}
                      onClick={() => {
                        handleItemClick([innerdata]);
                      }}
                    >
                      {parsedPaths.map((pathdata, index) => {
                        // FIXME: Should be recursive in here
                        const icon =
                          pathdata.type === "value" ? (
                            <VpnKeyIcon style={iconStyle} />
                          ) : pathdata.type === "list" ? (
                            <FormatListNumberedIcon style={iconStyle} />
                          ) : (
                            <ExpandMoreIcon style={iconStyle} />
                          )
      
                        return (
                          <MenuItem
                            key={pathdata.name}
                            style={{
                              backgroundColor: theme.palette.inputColor,
                              color: theme.palette.text.primary,
                              minWidth: 250,
                            }}
                            value={pathdata}
                            onMouseOver={() => { }}
                            onClick={() => {
                              handleItemClick([innerdata, pathdata]);
                            }}
                          >
                            <Tooltip
                              color="primary"
                              title={`Ex. value: ${pathdata.value}`}
                              placement="left"
                            >
                              <div style={{ display: "flex" }}>
                                {icon} {pathdata.name}
                              </div>
                            </Tooltip>
                          </MenuItem>
                        );
                      })}
                    </NestedMenuItem>
                    */}

                    <NestedMenuItem
                      key={innerdata.name}
                      label={
                        <div style={{ display: "flex", marginLeft: 0, }}>
                          {icon} {innerdata.name}
                        </div>
                      }
                      parentMenuOpen={!!menuPosition}
                      style={{
                        color: theme.palette.text.primary,
                        minWidth: 250,
                        maxWidth: 250,
                        maxHeight: 50,
                        overflow: "hidden",
                      }}
                      onClick={() => {
                        console.log(innerdata.example)
                        handleItemClick([innerdata]);
                      }}
                    >
                      <Paper style={{ minHeight: 500, maxHeight: 500, minWidth: 275, maxWidth: 275, position: "fixed", top: menuPosition?.top - 200, left: menuPosition?.left - 455, padding: "10px 0px 10px 10px", overflow: "hidden", overflowY: "auto", border: "1px solid rgba(255,255,255,0.3)", }}>

                        <MenuItem
                          key={innerdata.name}
                          style={{
                            // backgroundColor: theme.palette.inputColor,
                            marginLeft: 15,
                            color: theme.palette.text.primary,
                            minWidth: 250,
                            maxWidth: 250,
                            padding: 0,
                            position: "relative",
                          }}
                          value={innerdata}
                          onMouseOver={() => {
                            //console.log("HOVER: ", pathdata);
                          }}
                          onClick={() => {
                            handleItemClick([innerdata]);
                          }}
                        >
                          <Typography variant="h6" style={{ paddingBottom: 5 }}>
                            {innerdata.name}
                          </Typography>
                        </MenuItem>

                        {parsedPaths.map((pathdata, index) => {
                          // FIXME: Should be recursive in here
                          //<VpnKeyIcon style={iconStyle} />
                          const icon =
                            pathdata.type === "value" ? (
                              <span style={{ marginLeft: 9, }} />
                            ) : pathdata.type === "list" ? (
                              <FormatListNumberedIcon style={{ marginLeft: 9, marginRight: 10, }} />
                            ) : (
                              <CircleIcon style={{ marginLeft: 9, marginRight: 10, color: coverColor }} />
                            );
                          //<ExpandMoreIcon style={iconStyle} />

                          const indentation_count = (pathdata.name.match(/\./g) || []).length + 1
                          const baseIndent = <div style={{ marginLeft: 20, height: 30, width: 1, backgroundColor: coverColor, }} />
                          //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0
                          const boxPadding = 0
                          const namesplit = pathdata.name.split(".")
                          const newname = namesplit[namesplit.length - 1]
                          return (
                            <MenuItem
                              key={pathdata.name}
                              style={{
                                // backgroundColor: theme.palette.inputColor,
                                color: theme.palette.text.primary,
                                minWidth: 250,
                                maxWidth: 250,
                                padding: boxPadding,
                              }}
                              value={pathdata}
                              onMouseOver={() => {
                                //console.log("HOVER: ", pathdata);
                              }}
                              onClick={() => {
                                handleItemClick([innerdata, pathdata]);
                              }}
                            >
                              <Tooltip
                                color="primary"
                                title={`Ex. value: ${pathdata.value}`}
                                placement="left"
                              >
                                <div style={{ display: "flex", height: 30, }}>
                                  {Array(indentation_count).fill().map((subdata, subindex) => {
                                    return (
                                      baseIndent
                                    )
                                  })}
                                  {icon} {newname}
                                  {pathdata.type === "list" ? <SquareFootIcon style={{ marginleft: 10, }} onClick={(e) => {

                                  }} /> : null}
                                </div>
                              </Tooltip>
                            </MenuItem>
                          );
                        })}
                      </Paper>
                    </NestedMenuItem>
                  </span>
                ) : (
                  <MenuItem
                    key={innerdata.name}
                    style={{
                      // backgroundColor: theme.palette.inputColor,
                      color: theme.palette.text.primary,
                    }}
                    value={innerdata}
                    onMouseOver={() => handleMouseover()}
                    onMouseOut={() => {
                      handleMouseOut();
                    }}
                    onClick={() => {
                      handleItemClick([innerdata]);
                    }}
                  >
                    <Tooltip
                      color="primary"
                      title={`Value: ${innerdata.value}`}
                      placement="left"
                    >
                      <div style={{ display: "flex" }}>
                        {icon} {innerdata.name}
                      </div>
                    </Tooltip>
                  </MenuItem>
                );
              })}
          </Menu>
        }
        <div
          style={{
            marginTop: "20px",
            marginBottom: "7px",
            display: "flex",
          }}
        >
          <div style={{ flex: "10" }}>
            <b>Input options</b>
            <Typography variant="body2" color="textSecondary">
              Use subflows to connect to any app you want, or use the default email and sms options
            </Typography>
          </div>
        </div>
        <FormGroup
          style={{ paddingLeft: 10, backgroundColor: theme.palette.inputColor }}
          row
        >
          <FormControlLabel
            control={
              <Checkbox
                checked={workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")}
                onChange={() => {
                  setTriggerOptionsWrapper("subflow");
                }}
                color="primary"
                value="subflow"
              />
            }
            label={<div style={{ color: theme.palette.text.primary }}>Subflow</div>}
          />
          <FormControlLabel
            control={
              <Checkbox
                disabled={!isCloud}
                checked={
                  workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email")
                }
                onChange={() => {
                  setTriggerOptionsWrapper("email");
                }}
                color="primary"
                value="email"
              />
            }
            label={<div style={{ color: theme.palette.text.primary }}>Email</div>}
          />
          <FormControlLabel
            control={
              <Checkbox
                disabled={!isCloud}
                checked={workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers[selectedTriggerIndex].parameters !== undefined && workflow.triggers[selectedTriggerIndex].parameters.length > 0 && workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value !== undefined ? workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("sms") : false}
                onChange={() => {
                  setTriggerOptionsWrapper("sms");
                }}
                color="primary"
                value="sms"
                disabled={true}
              />
            }
            label={<div style={{ color: theme.palette.text.primary }}>SMS</div>}
          />
        </FormGroup>
        {workflow?.triggers &&
          workflow?.triggers[selectedTriggerIndex] &&
          workflow?.triggers[selectedTriggerIndex].parameters &&
          workflow?.triggers[selectedTriggerIndex].parameters[2] &&
          workflow?.triggers[selectedTriggerIndex].parameters[2].value &&
          workflow?.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")
          ? (
            <div style={{}}>
              {workflows === undefined ||
                workflows === null ||
                workflows.length === 0 ? null : (
                <Autocomplete
                  id="subflow_search"
                  autoHighlight
                  value={subworkflow}
                  classes={{ inputRoot: classes.inputRoot }}
                  ListboxProps={{
                    style: {
                      backgroundColor: theme.palette.inputColor,
                      color: theme.palette.text.primary,
                    },
                  }}
				  sx={{
					'& .MuiOutlinedInput-root': {
					  height: 40, // Adjust the input height
					},
					'& .MuiAutocomplete-input': {
					  padding: '8px', // Adjust the text padding
					},
				  }}
                  style={{
					backgroundColor: theme.palette.inputColor,
					borderRadius: theme.palette?.borderRadius,
                    marginTop: 15,
                    marginBottom: 15,
                  }}
                  getOptionSelected={(option, value) => option.id === value.id}
                  getOptionLabel={(option) => {
                    if (option === undefined || option === null || option.name === undefined || option.name === null) {
                      return "No Workflow Selected";
                    }

                    const newname = (option.name.charAt(0).toUpperCase() + option.name.substring(1)).replaceAll("_", " ");
                    return newname;
                  }}
                  options={
                    [{
                      "id": "",
                      "name": "No Workflow Selected",
                    }].concat(workflows)
                  }
                  fullWidth
                  onChange={(event, newValue) => {
                    console.log("Changed autocomplete!")
                    handleWorkflowSelectionUpdate({ target: { value: newValue } }, true)
                    event.target.blur();
                  }}
                  renderOption={(props, data, state) => {
                    if (data.id === workflow.id) {
                      data = workflow;
                    }

                    return (
                      <Tooltip arrow placement="left" title={
                        <span style={{}}>
                          {data.image !== undefined && data.image !== null && data.image.length > 0 ?
                            <img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette?.borderRadius, }} />
                            : null}
                          <Typography>
                            Choose Trigger '{data.name}'
                          </Typography>
                        </span>
                      }>
                        <MenuItem
                          style={{
                            color: data.id === workflow.id ? "red" : theme.palette.text.primary,
                          }}
                          value={data}
                          onClick={() => {
                            handleWorkflowSelectionUpdate({
                              target: {
                                value: data,
                              }
                            },
                              true)
                            document.activeElement.blur();
                          }}
                        >
                          <PolylineIcon style={{ marginRight: 8 }} />
                          {data.name}
                        </MenuItem>
                      </Tooltip>
                    )
                  }}
                  renderInput={(params) => {
                    return (
					  <div style={{display: "flex", }}>
						  <TextField
							style={theme.palette.textFieldStyle}
							{...params}
							label="Find the workflow you want to trigger"
							variant="outlined"
                      	  />

							{subworkflow === null || subworkflow === undefined || subworkflow?.id === undefined || subworkflow?.id === null || subworkflow?.id.length === 0 ? null :
								<Tooltip title="Show subflow in new window" placement="top">
									<a
									  rel="noopener noreferrer"
									  href={`/workflows/${subworkflow.id}`}
									  target="_blank"
									  style={{
										textDecoration: "none",
										color: "#FF8544",
										marginLeft: 5,
										marginTop: 10,
									  }}
									>
									  <OpenInNewIcon />
									</a>
								</Tooltip>
							}
						</div>
                    );
                  }}
                />
              )}

              {/* Button for making a new workflow to attach */}
              <Button
                variant="outlined"
                style={{ marginTop: 10, }}
                disabled={!(subworkflow === null || subworkflow === undefined || Object.getOwnPropertyNames(subworkflow).length === 0)}
                onClick={() => {
                  toast("Setting up new workflow")

                  const newworkflow = {
                    name: "User Input subflow",
                    description: "",
                  }

                  createWorkflow(newworkflow, selectedTriggerIndex)
                }}
                color="primary"
              >

                <AddIcon /> New Subflow
              </Button>

            </div>
          ) : null}

        {workflow?.triggers &&
          workflow?.triggers[selectedTriggerIndex] &&
          workflow?.triggers[selectedTriggerIndex].parameters &&
          workflow?.triggers[selectedTriggerIndex].parameters[2] &&
          workflow?.triggers[selectedTriggerIndex].parameters[2].value &&
          workflow?.triggers[selectedTriggerIndex].parameters[2].value.includes("email")
          ? (
            <TextField
              style={{
                backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                borderRadius: theme.palette?.textFieldStyle.borderRadius,
                marginTop: 10,
              }}
              InputProps={{
                style: {
                  color: theme.palette.textFieldStyle.color,
                  backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                  fontSize: "1em",
                },
              }}
              fullWidth
              label="Email"
              color="primary"
              required
              placeholder={"mail1@company.com,mail2@company.com"}
              defaultValue={
                workflow.triggers[selectedTriggerIndex].parameters[3].value
              }
              onBlur={(event) => {
                workflow.triggers[selectedTriggerIndex].parameters[3].value =
                  event.target.value;
                setWorkflow(workflow);
                setUpdate(Math.random());
              }}
            />
          ) : null}

        {workflow?.triggers &&
          workflow?.triggers[selectedTriggerIndex] &&
          workflow?.triggers[selectedTriggerIndex].parameters &&
          workflow?.triggers[selectedTriggerIndex].parameters[2] &&
          workflow?.triggers[selectedTriggerIndex].parameters[2].value &&
          (
            workflow?.triggers[selectedTriggerIndex].parameters[2].value.includes("sms")
          ) ? (
          <TextField
            style={{
              backgroundColor: theme.palette.inputColor,
              borderRadius: theme.palette?.borderRadius,
              marginTop: 10,
            }}
            InputProps={{
              style: {
              },
            }}
            fullWidth
            color="primary"
            placeholder={"+474823212132,+46020304242"}
            label="Phone Number"
            defaultValue={
              workflow.triggers[selectedTriggerIndex].parameters[4].value
            }
            onBlur={(event) => {
              workflow.triggers[selectedTriggerIndex].parameters[4].value =
                event.target.value;
              setWorkflow(workflow);
              setUpdate(Math.random());
            }}
          />
        ) : null}


        <div style={{ marginTop: 50, }} />
        <b>Required Input-Questions</b>
        {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
          <div>
            {workflow.input_questions.map((question, index) => {
              var foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.findIndex((param) => param.name === "input_questions")

              const selectionClick = () => {
                if (foundParamIndex === -1) {
                  workflow.triggers[selectedTriggerIndex].parameters.push({
                    "name": "input_questions",
                    "value": [],
                  })

                  foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.length - 1
                } else {
                  try {
                    workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.parse(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value)
                  } catch (e) {
                    console.log("Couldn't parse input questions: ", e)
                    workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = []
                  }
                }

                if (workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.includes(question.name)) {
                  workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.filter((item) => item !== question.name)
                } else {
                  workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.push(question.name)
                }

                // Make it back to a string
                workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.stringify(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value)
                setWorkflow(workflow)
                setUpdate(Math.random())
              }

              return (
                <div key={index} style={{ display: "flex", cursor: "pointer", }} onClick={() => {
                  selectionClick()
                }}>
                  <Checkbox
                    checked={foundParamIndex !== -1 ? workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.includes(question.name) : false}
                  />
                  <Typography variant="body2" style={{ marginTop: 10, }}>
                    {question.name}
                  </Typography>
                </div>
              )
            })}
          </div>
          :
          <div style={{ cursor: "pointer", color: "#FF8544", marginTop: 10, }} onClick={() => {
            setEditWorkflowModalOpen(true)
            toast.info("Expand and scroll down to add input-questions")
          }}>
            <Typography variant="body2">No Input-Questions found. Click to add them!</Typography>
          </div>
        }
      </div>

    </div>

  const defaultEnvironment = environments?.find(
    (env) => env.default && env.Name.toLowerCase() !== "cloud"
  )

  if (selectedTrigger.trigger_type === "PIPELINE" && selectedTrigger.environment === "onprem" && defaultEnvironment !== undefined) {
    selectedTrigger.environment = defaultEnvironment.Name
    setSelectedTrigger(selectedTrigger)
  }

  const PipelineSidebar = !selectedTrigger || Object.getOwnPropertyNames(selectedTrigger)?.length === 0 || !workflow?.triggers || !workflow?.triggers[selectedTriggerIndex] || !selectedTrigger.trigger_type || selectedTrigger.trigger_type !== "SCHEDULE" ? null :
    <div style={appApiViewStyle}>
      <h3 style={{ marginBottom: "5px" }}>
        {selectedTrigger.app_name}: {selectedTrigger.status}
      </h3>
      <a
        rel="noopener noreferrer"
        target="_blank"
        href="https://shuffler.io/docs/triggers#pipelines"
        style={{ textDecoration: "none", color: theme.palette.linkColor}}
      >
        What are pipelines?
      </a>
      <Divider
        style={{
          marginBottom: "10px",
          marginTop: "10px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div>Name</div>
      <TextField
        style={{
          backgroundColor: theme.palette.inputColor,
          borderRadius: theme.palette?.borderRadius,
        }}
        InputProps={{
          style: {},
        }}
        fullWidth
        color="primary"
        placeholder={selectedTrigger.label}
        onChange={selectedTriggerChange}
      />

      <div style={{ marginTop: "20px" }}>
        <Typography>Environment</Typography>
        <Select
          MenuProps={{
            disableScrollLock: true,
          }}
          value={selectedTrigger.environment}
          disabled={selectedTrigger.status === "running"}
          SelectDisplayProps={{}}
          fullWidth
          onChange={(e) => {
            selectedTrigger.environment = e.target.value;
            setSelectedTrigger(selectedTrigger);

            setWorkflow(workflow);
            setUpdate(Math.random());
          }}
          style={{
            backgroundColor: theme.palette.inputColor,
            color: theme.palette.text.primary,
            height: 50,
          }}
        >
          {environments.map((data) => {
            if (data.archived) {
              return null;
            }

            if (data.Name.toLowerCase() === "cloud") {
              return null;
            }

            return (
              <MenuItem
                key={data.id}
                style={{
                  backgroundColor: theme.palette.inputColor,
                  color: theme.palette.text.primary,
                }}
                value={data.Name}
              >
                {data.Name}
              </MenuItem>
            );
          })}
        </Select>
      </div>
      <Divider
        style={{
          marginTop: "20px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div style={{ flex: 6, marginTop: 20 }}>
        <div>
          <b>What would you like to do?</b>
          {/*
                <div
                  key="syslogListener"
                  onClick={() => {
                    if(selectedTrigger.status === "running"){
                      //toast("please stop the trigger to edit the configuration");
                      return;
                    } else {
                    setSelectedOption("Syslog listener");
                    const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
                    const command = `from tcp://192.168.1.100:5162 read syslog | import`
                    const pipelineConfig = {
                      command: command,
                      name: selectedTrigger.label,
                      type: "create",
                      environment: selectedTrigger.environment,
                      workflow_id: workflow.id,
                      trigger_id: selectedTrigger.id,
                      start_node: "",
                      url:url,
                    };
                    submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
                 
                  
                  }}}
                  style={{
                    border: "1px solid rgba(255,255,255,0.3)",
                    borderRadius: theme.palette?.borderRadius,
                    padding: 10,
                    cursor: "pointer",
                    marginTop: 5,
                    display: "flex",
                    alignItems: "center",
                  }}
                >
                  <FormControlLabel
                    control={
                      <Radio
                        checked={selectedOption === "Syslog listener"}
                        onChange={() => {
                          setSelectedOption("Syslog listener")}}

                        value={"Syslog listener"}
                        name="option"
                      />
                    }
                    label= {selectedOption === "Syslog listener" && selectedTrigger.status === "running" ? "listening at 192.168.1.100:5162" : "Start Syslog listener"}
                  />
                </div>

                <div
                  key="sigmaRulesearch"
                  onClick={() => {
                    if(selectedTrigger.status === "running"){
                     // toast("please stop the trigger to edit the configuration");
                      return;
                    } else {
                    setSelectedOption("SigmaRule");
                    const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
                    const command = `export | sigma /var/lib/tenzir/sigma_rules | to ${url}`
                    const pipelineConfig = {
                      command: command,
                      name: selectedTrigger.label,
                      type: "create",
                      environment: selectedTrigger.environment,
                      workflow_id: workflow.id,
                      trigger_id: selectedTrigger.id,
                      start_node: "",
                      url:url,
                    };
                    submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
                 
                  }}}
                  style={{
                    border: "1px solid rgba(255,255,255,0.3)",
                    borderRadius: theme.palette?.borderRadius,
                    padding: 10,
                    cursor: "pointer",
                    marginTop: 5,
                    display: "flex",
                    alignItems: "center",
                  }}
                >
                  <FormControlLabel
                    control={
                      <Radio
                        checked={selectedOption === "SigmaRule"}
                        onChange={() => {
                          if (selectedTrigger.status !== "running"){
                          	setSelectedOption("SigmaRule")
						  }
						}}
                        value={"Sigma Rulesearch"}
                        name="option"
                      />
                    }
                    label="Run Sigma Rulesearch"
                  />
                </div>

				*/}

          <div
            key="kafkaQueue"
            onClick={() => {
              if (selectedTrigger.status === "running") {
                toast("please stop the trigger to edit the configuration");
                return;
              } else {
                setSelectedOption("Kafka Queue");
                setTenzirConfigModalOpen(true);
              }
            }}
            style={{
              border: "1px solid rgba(255,255,255,0.3)",
              borderRadius: theme.palette?.borderRadius,
              padding: 10,
              cursor: "pointer",
              marginTop: 5,
              display: "flex",
              alignItems: "center",
            }}
          >
            <FormControlLabel
              control={
                <Radio
                  checked={selectedOption === "Kafka Queue"}
                  onChange={() => {
                    if (selectedTrigger.status !== "running") {
                      setSelectedOption("Kafka Queue")

                    }
                  }}

                  value={"Kafka Queue"}
                  name="option"
                />
              }
              label="Subscribe to a Kafka Queue"
            />
          </div>

          <div style={{ marginTop: 20, marginBottom: 7, display: "flex" }}>
            <Button
              style={{ flex: 1 }}
              variant="contained"
              disabled={selectedTrigger.status !== "running"}
              onClick={() => {
                const pipelineConfig = {
                  name: selectedTrigger.label,
                  type: "stop",
                  environment: selectedTrigger.environment,
                  workflow_id: workflow.id,
                  trigger_id: selectedTrigger.id,
                  start_node: "",
                };
                submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
              }}
              color="primary"
            >
              Stop
            </Button>
          </div>
        </div>
      </div>
    </div>

  const ScheduleSidebar = !selectedTrigger || Object.getOwnPropertyNames(selectedTrigger)?.length === 0 || !workflow?.triggers || !workflow?.triggers[selectedTriggerIndex] && (!selectedTrigger.trigger_type || selectedTrigger.trigger_type !== "SCHEDULE") ? null :
    <div style={appApiViewStyle}>
      <h3 style={{ marginBottom: "5px" }}>
        {selectedTrigger.app_name}: {selectedTrigger.status}
      </h3>
      <a
        rel="noopener noreferrer"
        target="_blank"
        href="https://shuffler.io/docs/triggers#schedule"
        style={{ textDecoration: "none", color: theme.palette.linkColor }}
      >
        What are schedules?
      </a>
      <Divider
        style={{
          marginBottom: "10px",
          marginTop: "10px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div>Name</div>
      <TextField
        style={{
          backgroundColor: theme.palette.textFieldStyle.backgroundColor,
          borderRadius: theme.palette?.textFieldStyle.borderRadius,
        }}
        InputProps={{
          style: {
            backgroundColor: theme.palette.textFieldStyle.backgroundColor,
            color: theme.palette.textFieldStyle.color,
          },
        }}
        fullWidth
        color="primary"
        placeholder={selectedTrigger.label}
        defaultValue={selectedTrigger?.label}
        onChange={selectedTriggerChange}
		disabled={selectedTrigger?.parent_controlled === true && workflow?.parentorg_workflow?.length > 0}
      />
      <div style={{ marginTop: "20px" }}>
        <Typography>Environment</Typography>
        <Select
          MenuProps={{
            disableScrollLock: true,
          }}
          value={selectedTrigger.environment}
          disabled={selectedTrigger.status === "running"}
          SelectDisplayProps={{
            style: {
            },
          }}
          fullWidth
          onChange={(e) => {
            selectedTrigger.environment = e.target.value;
            if (e.target.value === "cloud") {
              console.log("Set cloud config");
              selectedTrigger.parameters[0].value = "*/25 * * * *";
              workflow.triggers[selectedTriggerIndex].parameters[0].value =
                "*/25 * * * *";
            } else {
              console.log("Set cloud config");

              workflow.triggers[selectedTriggerIndex].parameters[0].value =
                "60";
            }
            setSelectedTrigger(selectedTrigger);
            setWorkflow(workflow);
            setUpdate(Math.random());
          }}
          style={{
            backgroundColor: theme.palette.textFieldStyle.backgroundColor,
            color: theme.palette.textFieldStyle.color,
            height: 50,
          }}
        >
          {triggerEnvironments.map((data) => {
            if (data.archived) {
              return null;
            }

            return (
              <MenuItem
                key={data}
                style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor, color: theme.palette.textFieldStyle.color}}
                value={data}
              >
                {data}
              </MenuItem>
            );
          })}
        </Select>
      </div>
      <Divider
        style={{
          marginTop: "20px",
          height: "1px",
          width: "100%",
          backgroundColor: "rgb(91, 96, 100)",
        }}
      />
      <div style={{ flex: "6", marginTop: "20px" }}>
        <div>
          <b>Parameters</b>
          <div
            style={{
              marginTop: "20px",
              marginBottom: "7px",
              display: "flex",
            }}
          >
            <div
              style={{
                width: "17px",
                height: "17px",
                borderRadius: 17 / 2,
                backgroundColor: "#FF8544",
                marginRight: "10px",
              }}
            />
            <div style={{ flex: "10" }}>
              <b>When to start: {isCloud || selectedTrigger?.environment === "cloud" ? <a href="https://crontab.guru" target="_blank" style={{ color: theme.palette.linkColor, }}>Cron formatting</a> : "every X second"}</b>
            </div>
          </div>
          <TextField
            style={{
              backgroundColor: theme.palette.textFieldStyle.backgroundColor,
              borderRadius: theme.palette?.textFieldStyle.borderRadius,
            }}
            InputProps={{
              style: {
                backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                color: theme.palette.textFieldStyle.color,
              },
            }}
            fullWidth
            disabled={
              selectedTrigger.status === "running"
            }
            defaultValue={
              selectedTrigger?.parameters === undefined || selectedTrigger?.parameters === null || selectedTrigger?.parameters?.length === 0 ? isCloud || selectedTrigger?.environment === "cloud" ? "*/25 * * * *" : "60" : selectedTrigger?.parameters[0]?.value
            }
            color="primary"
            placeholder={
				selectedTrigger.parameters === undefined || selectedTrigger?.parameters === null || selectedTrigger?.parameters?.length === 0 ? isCloud || selectedTrigger?.environment === "cloud" ? "*/25 * * * *" : "60" : selectedTrigger?.parameters[0]?.value
			}
            onBlur={(e) => {
              setTriggerCronWrapper(e.target.value);
            }}
          />
          {/*selectedTrigger.environment === "cloud" ? 
								<Cron
      					  value={workflow.triggers[selectedTriggerIndex].parameters[0].value}
      					  setValue={setTriggerCronWrapper}
      					/>
							: 
								null
							*/}
          <div
            style={{
              marginTop: "20px",
              marginBottom: "7px",
              display: "flex",
            }}
          >
            <div
              style={{
                width: "17px",
                height: "17px",
                borderRadius: 17 / 2,
                backgroundColor: "#FF8544",
                marginRight: "10px",
              }}
            />
            <div style={{ flex: "10" }}>
              <b>Runtime Argument: </b>
            </div>
          </div>
          <TextField
            style={{
              backgroundColor: theme.palette.textFieldStyle.backgroundColor,
              borderRadius: theme.palette?.textFieldStyle.borderRadius,
            }}
            InputProps={{
              style: {
                backgroundColor: theme.palette.textFieldStyle.backgroundColor,
                color: theme.palette.textFieldStyle.color,
              },
            }}
            disabled={
              workflow.triggers[selectedTriggerIndex] === null || workflow.triggers[selectedTriggerIndex] === undefined ? false : workflow.triggers[selectedTriggerIndex].status === "running"
            }
            fullWidth
            rows="3"
            multiline
            color="primary"
            defaultValue={
              workflow.triggers[selectedTriggerIndex] !== undefined && workflow.triggers[selectedTriggerIndex].parameters !== undefined && workflow.triggers[selectedTriggerIndex].parameters !== null && workflow.triggers[selectedTriggerIndex].parameters.length > 1 ? workflow.triggers[selectedTriggerIndex].parameters[1].value : ""
            }
            placeholder='{"key": "value"}'
            onBlur={(e) => {
              setTriggerBodyWrapper(e.target.value);
            }}
          />
          <Divider
            style={{
              marginTop: "20px",
              height: "1px",
              width: "100%",
              backgroundColor: "rgb(91, 96, 100)",
            }}
          />
          <div
            style={{
              marginTop: "20px",
              marginBottom: "7px",
              display: "flex",
            }}
          >
            <Button
              style={{ flex: "1" }}
              variant="contained"
              disabled={selectedTrigger.status === "running"}
              onClick={() => {
                submitSchedule(selectedTrigger, selectedTriggerIndex);
              }}
              color="primary"
            >
              Start
            </Button>
            <Button
              style={{ flex: "1" }}
              variant="contained"
              disabled={selectedTrigger.status !== "running"}
              onClick={() => {
                stopSchedule(selectedTrigger, selectedTriggerIndex);
              }}
              color="primary"
            >
              Stop
            </Button>
          </div>
        </div>
      </div>
    </div>

  const cytoscapeViewWidths = isMobile ? 50 : 950;
  const bottomBarStyle = {
    position: "absolute",
    left: `${leftBarSize + (cytoscapeWidth / 2)}px`,
    transform: "translateX(-50%)",
    bottom: 20,
    zIndex: 10,
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
  };

  const topBarStyle = {
    position: "absolute",
    top: 10,
    transform: `translateX(${leftBarSize}px)`,
    width: bodyWidth - leftBarSize + 25,
    transition: "all 0.3s ease",
    paddingLeft : 30,
    zoom: isSafari ? undefined : 0.9,
    overflow: "hidden",
  } 


	const changeOrg = () => {
		localStorage.setItem("globalUrl", "");
		localStorage.setItem("getting_started_sidebar", "open");
		fetch(`${globalUrl}/api/v1/orgs/${workflow.org_id}/change`, {
		  mode: "cors",
		  credentials: "include",
		  crossDomain: true,
		  method: "POST",
		  body: JSON.stringify({ "org_id": workflow.org_id }),
		  withCredentials: true,
		  headers: {
			"Content-Type": "application/json; charset=utf-8",
		  },
		})
		  .then(function (response) {
			if (response.status !== 200) {
			  console.log("Error in response");
			} else {
			  localStorage.removeItem("apps")
			  localStorage.removeItem("workflows")
			  localStorage.removeItem("userinfo")
			}

			return response.json();
		  })
		  .then(function (responseJson) {
			console.log("In here?")
			if (responseJson.success === true) {
			  if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) {
				console.log("Region Change: ", responseJson.region_url);
				localStorage.setItem("globalUrl", responseJson.region_url);
				//globalUrl = responseJson.region_url
			  }

			  if (responseJson["reason"] === "SSO_REDIRECT") {
				setTimeout(() => {
				  toast.info("Redirecting to SSO login page as SSO is required for this organization.")
				  window.location.href = responseJson["url"]
				  return
				}, 2000)
			  } else {
				setTimeout(() => {
				  window.location.reload();
				}, 2000);
			  }

			  toast.success("Successfully changed active organisation - refreshing!");
			} else {
			  if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
				toast(responseJson.reason);
			  } else {
				toast(`Failed changing org. Try again or contact ${supportEmail} if this persists.`);
			  }
			}
		  })
		  .catch((error) => {
			console.log("error changing: ", error);
			//removeCookie("session_token", {path: "/"})
		  })
		}

  const TopCytoscapeBar = (props) => {
    const [hovered, setHovered] = useState(false)

     const toggleButtonSx = {
        textTransform: "none",
        backgroundColor: "transparent",
        color: theme.palette.text.primary,
        height: 36,
        minWidth: 90,
        padding: "8px 44px",
        display: "flex",
        alignItems: "center",
        gap: 1,
        fontSize: "16px",
        fontWeight: 400,
        boxShadow: "none",
        fontFamily: theme?.typography?.fontFamily,
        "&.Mui-selected": {
          backgroundColor: themeMode === "dark" ? "#1e1e1e" : "#CCCCCC",
          color: theme.palette.text.primary,
          fontWeight: 600,
          "&:hover": {
              backgroundColor: themeMode === "dark" ? "rgba(0,0,0,0.3)" : "rgba(0,0,0,0.1)",
            },
        },
        "&:hover": {
          backgroundColor: themeMode === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)",
        },
      }

      const toggleOptions = [
          { value: "build", label: "Build", icon: "/icons/workflow-page/buildIcon.svg", darkIcon: "/icons/workflow-page/buildIcon-dark.svg"},
          { value: "debug", label: "Debug", icon: "/icons/workflow-page/runningMan.svg", darkIcon: "/icons/workflow-page/run-dark.svg"},
      ]

    if (workflow?.public === true) {
      return null
    }

    if (userdata?.active_org === undefined || userdata?.active_org === null) {
      return null
    }

    const isCorrectOrg = workflow?.public === true || userdata?.active_org.id === undefined || userdata?.active_org.id === null || workflow?.org_id === null || workflow?.org_id === undefined || workflow?.org_id?.length === 0 || userdata?.active_org?.id === workflow?.org_id

    return (
      <div style={topBarStyle}>
      <div style={{
        width: "100%",
        padding: "10px 20px",
        position: "relative",
        boxSizing: "border-box",
        overflow: "hidden",
      }}>
        {/* Top Row: Workflow Name | Build/Debug Toggle | Right Side Buttons */}
        <div style={{
          display: "flex",
          flexDirection: "row",
          alignItems: "flex-start",
          justifyContent: "space-between",
          width: "100%",
          position: "relative",
          minHeight: 80,
        }}>
          {/* Left: Workflow Name Container */}
          <div style={{ 
            flexShrink: 0, 
            display: "flex", 
            flexDirection: "column",
            gap: "8px",
            minWidth: 200,
            maxWidth: 400,
          }}>
            <Typography variant="h6" style={{
              margin: 0,
              cursor: "pointer",
              display: "flex",
              borderRadius: theme.palette.borderRadius,
              border: hovered ? "1px solid rgba(255,255,255,0.3)" : "1px solid transparent",
              paddingRight: 10,
              paddingLeft: 10,
              position: "relative",
              height: 40,
              alignItems: "center",
            }}
              onMouseEnter={() => {
                setHovered(true)
              }}
              onMouseLeave={() => {
                setHovered(false)
              }}
              onClick={() => {
                setEditWorkflowModalOpen(true)
                setLastSaved(false)
              }}
            >
              {workflow?.name !== undefined && workflow?.name !== null && workflow?.name?.length > 0 ?
                <EditIcon style={{ position: "absolute", top: 7, height: 20, width: 20, }} />
                : 
                null
              }
              <span style={{ marginLeft: 30, }}>{workflow.name}</span>
            </Typography>
          </div>
  
          {/* Center: Build/Debug Toggle */}
          {workflow.public === true ? null : (
          <div style={{
                position: "absolute",
                left: "50%",
                transform: "translateX(-50%)",
                top: 0,
                zIndex: 1,
              }}>
                <ToggleButtonGroup
                  value={buildDebugMode}
                  exclusive
                  onChange={(event, newMode) => {
                    if (newMode !== null) {
                      setBuildDebugMode(newMode)
                      if (newMode === "debug") {
                        setExecutionModalOpen(true);
                        getWorkflowExecution(workflow.id, "", executionFilter, workflow.org_id)
                      }
                    }
                  }}
                  sx={{
                    backgroundColor: themeMode === "dark" ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.05)",
                    borderRadius: "8px",
                    padding: "3px",
                    border: "none",
                    "& .MuiToggleButtonGroup-grouped": {
                      border: "none",
                      "&:not(:first-of-type)": {
                        borderRadius: "6px",
                        borderTopLeftRadius: "0px",
                        borderBottomLeftRadius: "0px",
                      },
                      "&:first-of-type": {
                        borderRadius: "6px",
                        borderTopRightRadius: "0px",
                        borderBottomRightRadius: "0px",
                      },
                    },
                  }}
                >
                  {toggleOptions.map(({ value, label, icon, darkIcon }) => (
                    <ToggleButton
                      key={value}
                      value={value}
                      sx={toggleButtonSx}
                    >
                      <img
                        src={themeMode !== "dark" ? darkIcon : icon}
                        alt={label}
                        style={{
                          width: 15,
                          height: 15,
                        }}
                      />
                      {label}
                    </ToggleButton>
                  ))}
                </ToggleButtonGroup>
          </div>)}
  
          {/* Right: Buttons Container */}
          <div style={{ 
            display: "flex", 
            flexDirection: "column", 
            gap: "8px", 
            alignItems: "flex-end",
            flexShrink: 0,
            minWidth: 200,
          }}>
            {originalWorkflow?.suborg_distribution === undefined || originalWorkflow?.suborg_distribution === null || originalWorkflow?.suborg_distribution?.length === 0 || originalWorkflow?.suborg_distribution.includes("none") ? 
              <>
                {originalWorkflow?.parentorg_workflow !== undefined && originalWorkflow?.parentorg_workflow !== null && originalWorkflow?.parentorg_workflow?.length > 0 || workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow?.length > 0 ?
                  null

                  /*
                   * Disabled because redirects occurred anyway
                   *
                  <Button
                    color="secondary"
                    variant="outlined"
                    style={{
                        marginTop: 10, 
                        marginBottom: 10, 
                        textTransform: "none",
                        marginLeft: 10, 
                    }}
                    onClick={() => {
                      //changeOrg() 

                      // Reload the page
                      navigate(`/workflows/${workflow.parentorg_workflow}`)
                      window.location.reload()
                    }}
                  >
                    Go to parent org workflow
                  </Button>
                  */

                  : null}

                {userdata !== undefined && userdata !== null && userdata?.orgs !== undefined && userdata?.orgs !== null && userdata?.orgs?.length > 1 && workflow?.id !== undefined && workflow?.id && workflow?.id?.length > 0 && userdata?.active_org?.creator_org?.length === 0 && userdata?.active_org?.id == workflow?.org_id ?
                  <Button
                    variant="contained"
                    color="secondary"
                    style={{
                      textTransform: "none",
                      height: 40,
                      borderRadius: theme.palette?.borderRadius,
                      color: theme.palette.text.primary,
                      width: "100%",
                    }}
                    onClick={() => {
                      setEditWorkflowModalOpen(true)
                    }}
                  >
                    Enable Suborg Distribution
                  </Button>
                : null}
              </>
              :
              <Tooltip title={lastSaved === false && originalWorkflow.id === workflow.id ? 
                <Typography variant="body1" style={{margin: 10, color: theme.palette.text.primary, }}>
                  Save the workflow first 
                </Typography>
                : null} arrow placement="right">
                <FormControl style={{ 
                  minWidth: 230, 
                  maxWidth: 230, 
                  pointerEvents: "auto", 
                  flexShrink: 0,
                  width: "100%",
                }}>
                  <InputLabel
                    id="suborg-changer"
                    style={{ color: theme.palette.textColor, }}
                  >
                    Select an Org ({originalWorkflow?.suborg_distribution?.length})
                  </InputLabel>
                  <Select
                    style={{
                      pointerEvents: "auto",
                      backgroundColor: theme.palette.inputColor,
                      color: theme.palette.text.primary,
                      borderRadius: theme.palette?.borderRadius,
                      backgroundColor: theme.palette.platformColor,
                      height: 40,
                    }}
                    MenuProps={{
                      PaperProps: {
                        sx: {
                          '& .MuiList-root': {
                            backgroundColor: theme.palette.platformColor,
                          },
                        }}
                    }}
                    InputProps={{
                      style: {
                        height: 40,
                      }
                    }}
                    labelId="suborg-changer"
                    value={workflow.org_id}
                    disabled={savingState !== 0 || suborgWorkflows?.length === 0 || allTriggers === undefined}
                    onChange={(e) => {
                      if (cy !== undefined && cy !== null) {
                        cy.nodes().unselect()
                      }
  
                      if (lastSaved === false && originalWorkflow.id === workflow.id) {
                        setSuborgWorkflows([])
                        saveWorkflow(workflow, undefined, undefined, e.target.value)

                        /* Standard re-loads */
                        setAllTriggers(undefined)
                        setSelectedTriggerIndex(-1)
                        getEnvironments(e.target.value)
                        getAppAuthentication(undefined, undefined, undefined, e.target.value)
                        getFiles(e.target.value)
                        listOrgCache(e.target.value)
                        /* Standard re-loads */

                        toast.warn(`Saving workflow first due to detected changes.`, {
                          autoClose: 2000,
                        })
                        return
                      }
  
                      if (workflow.org_id === e.target.value) {
                        console.log("Same org selected. No change.")
                        return
                      } else {
                        //if (savingState === 0) {
                        //  saveWorkflow(workflow, undefined, undefined, undefined)
                        //  return
                        //}
                      }
  
                      navigate(`?org_id=${e.target.value}`)
  
                      // Unselect in cy
                      if (cy !== undefined && cy !== null) {
                        cy.nodes().unselect()
                        cy.edges().unselect()
                      }
  
                      ReactDOM.unstable_batchedUpdates(() => {
                        /* Standard re-loads */
                        setAllTriggers(undefined)
                        setSelectedTriggerIndex(-1)
                        getEnvironments(e.target.value)
                        getAppAuthentication(undefined, undefined, undefined, e.target.value)
                        getFiles(e.target.value)
                        listOrgCache(e.target.value)
                        /* Standard re-loads */

                        // Reset the save button to ensure random saves don't occur during move
                        setLastSaved(true)
  
                        // FIXME: There is a timing problem here. 
                        // For events to have the data they need, they 
                        // need to be registered with setupGraph()
                        // AFTER all the APIs are done
                        
                        // Should look through childorg workflow
                        setTimeout(() => {
                          if (e.target.value === originalWorkflow.org_id) {
                            updateCurrentWorkflow(originalWorkflow)
                            return
                          } else {
                            // Load environments, auth, auth groups
                            //toast("Loading correct info for suborg")
                          }
  
                          if (originalWorkflow?.childorg_workflow_ids === undefined || originalWorkflow?.childorg_workflow_ids === null || originalWorkflow?.childorg_workflow_ids?.length === 0) {
                            //console.log("Childorg doesn't exist (?). Suborgworkflows: ", suborgWorkflows)

                            if (suborgWorkflows !== undefined && suborgWorkflows !== null && suborgWorkflows?.length > 0) {
                              var found = false
                              for (var suborgkey in suborgWorkflows) {
                                const suborgWorkflow = suborgWorkflows[suborgkey]
                                if (suborgWorkflow.org_id === e.target.value) {
                                  found = true
                                  updateCurrentWorkflow(suborgWorkflow)
                                  break
                                }
                              }
  
                              if (!found) {
                                toast("(3) Creating new workflow for this org. Please wait a second while we duplicate.")
                                //console.log("No workflow found out of suborg workflows.")
                                
                                //saveWorkflow(originalWorkflow, undefined, undefined, e.target.value)
                              }
                            } else {
                              console.log("Suborgworkflows: ", suborgWorkflows)
                              toast("(1) Loading NEW  workflow for this org (?). Please wait a second.")
                              saveWorkflow(originalWorkflow, undefined, undefined, e.target.value)
                            }
                          } else {
                            console.log("In childorg EXIST!")
                            var workflowFound = false
                            for (var childorgidkey in originalWorkflow.childorg_workflow_ids) {
                              const childworkflowid = originalWorkflow.childorg_workflow_ids[childorgidkey]
                              for (var suborgWorkflowKey in suborgWorkflows) {
                                const suborgWorkflow = suborgWorkflows[suborgWorkflowKey]
                                if (suborgWorkflow.org_id === e.target.value) {
                                  workflowFound = true
                                  updateCurrentWorkflow(suborgWorkflow)
                                  break
                                }
                              }
  
                              if (workflowFound) {
                                break
                              }
                            }
  
                            if (!workflowFound) {
                              console.log("No workflow found.")
                              toast("(2) Creating new workflow for this org. Please wait a few seconds while we prepare it for you.")
                              //saveWorkflow(originalWorkflow, undefined, undefined, e.target.value)
                            }
                          }
                        }, 500)
                      })
                    }}
                    label="Suborg Distribution"
                    fullWidth
                  >
                    <MenuItem 
                      key={originalWorkflow.org_id} 
                      value={originalWorkflow.org_id}
                    >
                      <Chip
                        style={{ marginLeft: 0, padding: 0, marginRight: 0, backgroundColor: theme.palette.slateGrayColor, color: theme.palette.text.primary  }}
                        label={"Parent"}
                        variant="outlined"
                        color="secondary"
                        onClick={(e) => {
                          e.preventDefault()
                          e.stopPropagation()
                        }}
                      /> {userdata.active_org.large_image}{" "}
                      <span style={{ marginLeft: 8 }}>
                        {userdata.active_org.name}
                      </span>
                    </MenuItem>
                    <Divider style={{ marginTop: 10, marginBottom: 10, }} />
                    {originalWorkflow.suborg_distribution.map((org_id, index) => {
                      var data = {}
                      for (var key in userdata.orgs) {
                        if (userdata.orgs[key].id === org_id) {
                          data = userdata.orgs[key]
                          break
                        }
                      }
  
                      if (data.id === undefined || data.id === null) {
                        return null
                      }
  
                      var skipOrg = false;
                      const imagesize = 22
                      const imageStyle = {
                        width: imagesize,
                        height: imagesize,
                        pointerEvents: "none",
                        marginRight: 10,
                        marginLeft:
                          data.creator_org !== undefined &&
                            data.creator_org !== null &&
                            data.creator_org?.length > 0
                            ? data?.id === userdata?.active_org?.id
                              ? 0
                              : 0
                            : 0,
                      }
  
                      const image =
                        data.image === "" ? (
                          <img
                            alt={data.name}
                            src={theme.palette.defaultImage}
                            style={imageStyle}
                          />
                        ) : (
                          <img
                            alt={data.name}
                            src={data.image}
                            style={imageStyle}
                          />
                        )
  
                      var orgDiff = {
                        different: false,
                      }
  
                      const foundMatchingWorkflow = suborgWorkflows?.find((workflow) => workflow.org_id === data.id)
                      if (foundMatchingWorkflow !== undefined && foundMatchingWorkflow !== null && foundMatchingWorkflow.diff !== undefined && foundMatchingWorkflow.diff !== null) {
                        orgDiff = foundMatchingWorkflow.diff
                      }
  
                      //console.log("DIFF: ", orgDiff)

                      return (
                        <MenuItem key={index} value={data.id} style={{display: "flex", }}>
                          <span style={{flex: 10, }}>
                            {image}{" "}
                            <span style={{ marginLeft: 8 }}>
                              {data.name}
                            </span>
                          </span>
  
                          {foundMatchingWorkflow !== undefined && foundMatchingWorkflow !== null && foundMatchingWorkflow?.errors !== undefined && foundMatchingWorkflow?.errors !== null && foundMatchingWorkflow?.errors?.length > 0 &&
                            <Tooltip placement="right" sx={{ maxWidth: 500, }}
                              title={
                                <div style={{ overflow: "auto", }}>
                                  <Typography variant="body1" style={{margin: 16, }}>
                                    <b>{foundMatchingWorkflow?.errors?.length} Workflow Issue{foundMatchingWorkflow?.errors?.length > 1 ? "s" : ""}</b>
                                  </Typography>
                                </div>
                              }
                            > 
                              <WarningIcon style={{color: theme.palette.text.secondary,  marginLeft: 25, flex: 1, }}/>
                            </Tooltip>
                          }
  
                          {orgDiff.different === true ? 
                            <Tooltip placement="right" sx={{ maxWidth: 500, }} 
                              componentsProps={{
                                tooltip: {
                                  sx: { maxWidth: 500, whiteSpace: "normal" }
                                }
                              }}
                              title={
                                <div style={{ overflow: "auto", }}>
                                  <Typography variant="body1" style={{margin: 16, }}>
                                    <b>Diff (beta):</b>
                                    <br/>
  
                                    {orgDiff.environment === true &&
                                      <span>- Environment<br/></span>
                                    }
  
                                    {orgDiff?.actions?.length > 0 &&
                                      <span>- Actions ({orgDiff.actions?.length}): <br/>
                                        {orgDiff.actions.map((orgDiffAction, index) => {
                                          //console.log("DIFF2: ", orgDiffAction)
                                          var formattedError = ""
                                          var paramchanges = ""
                                          for (var diffActionKey in orgDiffAction) {
                                            if (diffActionKey !== "id" && diffActionKey !== "label" && diffActionKey !== "parameters" && diffActionKey !== "params" && diffActionKey !== "large_image") {
                                              if (formattedError?.length > 0) {
                                                formattedError += ", "
                                              }
                                              formattedError += diffActionKey 
                                            }
  
                                            if (diffActionKey === "parameters") {
                                              paramchanges = orgDiffAction[diffActionKey].join(", ")
                                            }
                                          }
  
                                          if (paramchanges?.length > 0) {
                                            formattedError += paramchanges 
                                          }
  
                                          return (
                                            <li key={index}> 
                                              <Tooltip title={orgDiffAction.label} arrow placement="left">
                                                <img alt={orgDiffAction.label} src={orgDiffAction.large_image} style={{width: 20, height: 20, borderRadius: theme.palette.borderRadius, marginRight: 10, }}/>
                                              </Tooltip>
                                              {formattedError}
                                            </li>
                                          )
                                        })}
                                      </span>
                                    }
                                  </Typography>
                                </div>
                              }>
                              <DifferenceIcon style={{color: theme.palette.distributionColor, marginLeft: 25, flex: 1, }}/>
                            </Tooltip>
                          : null}
                        </MenuItem>
                      )
                    })}
                  </Select>
                </FormControl>
              </Tooltip>
            }

            {showEnvironment === true && environments?.length > 0 ?
              <Tooltip 
                arrow
                title={
                  workflow?.suborg_distribution?.length > 0 && Object.getOwnPropertyNames(selectedActionEnvironment || {})?.length !== 0 && selectedActionEnvironment?.Name !== "Cloud" && savingState === 0 ? (
                    <React.Fragment>
                      <div style={{padding: 10, backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, border: "1px solid rgba(255,255,255,0)"}}>
                        <FormControlLabel
                          control={
                            <Checkbox
                              checked={selectedActionEnvironment.suborg_distribution !== undefined && selectedActionEnvironment.suborg_distribution !== null && selectedActionEnvironment.suborg_distribution.length > 0}
                              onChange={(event) => {
                                toast("Opening in new tab. Refresh this page after adding it.")
  
                                setTimeout(() => {
                                  window.open(`/admin?tab=locations`, "_blank", "noopener,noreferrer")
                                }, 1500)
                                //selectedActionEnvironment.suborg_distribution = event.target.checked ? userdata.orgs.map((org) => org.id) : []
											          //changeDistribution(selectedAction?.selectedAuthentication)
                              }}
                              name="distributeAuth"
                              color="primary"
                            />
                          }
                          label={
                            <Typography style={{marginTop: 0, }}>
                              Distribute location <OpenInNewIcon style={{marginLeft: 10, color: theme.palette.main}}/>
                            </Typography>
                          }
                        />
                      </div>
                    </React.Fragment>
                  ) : null
                } placement="right">
                <FormControl style={{ 
                  minWidth: 230, 
                  maxWidth: 230, 
                  pointerEvents: "auto", 
                  flexShrink: 0,
                  marginTop: 10,
                }}>
                  <InputLabel
                    id="execution_location"
                    style={{ color: theme.palette.text.primary }}
                  >
                    Runtime Location
                  </InputLabel>
                  <Select
                    labelId="execution_location"
                    disabled={savingState !== 0}
                    MenuProps={{
                      PaperProps: {
                        sx: {
                          '& .MuiList-root': {
                            backgroundColor: theme.palette.platformColor,
                          },
                        }}
                    }}
                    value={
                      selectedActionEnvironment === undefined || selectedActionEnvironment === null || selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null ? isCloud ? "Cloud" : "Shuffle" : selectedActionEnvironment.Name
                    }
                    SelectDisplayProps={{
                      style: {
                      },
                    }}
                    InputProps={{
                      style: {
                        height: 40,
                      }
                    }}
                    onClick={(e) => {
                      getEnvironments(workflow.org_id)
                    }}
                    onChange={(e) => {
                      setLastSaved(false)
                      const env = environments.find((a) => a.Name === e.target.value)
                      setSelectedActionEnvironment(env)
                      selectedAction.environment = env.Name
                      setSelectedAction(selectedAction)
  
                      for (let actionkey in workflow.actions) {
                        workflow.actions[actionkey].environment = env.Name
                      }
  
                      setWorkflow(workflow)
                      //toast.success("Set execution location for ALL actions to " + env.Name)
                    }}
                    style={{
                      pointerEvents: "auto",
                      color: theme.palette.text.primary,
                      borderRadius: theme.palette?.borderRadius,
                      backgroundColor: theme.palette.platformColor,
                      height: 40,
                    }}
                  >
                    {environments.map((data, index) => {
                      if (data.archived === true) {
                        return null
                      }
  
                      const isRunning = data.running_ip !== ""
  
                      return (
                        <MenuItem
                          key={data.Name}
                          sx={{
                            backgroundColor:"transparent",
                            color: theme.palette.text.primary,
                          }}
                          value={data.Name}
                        >
                          {data.Name === "cloud" || data.Name === "Cloud" ? null : !isRunning ?
                            <a href={`/admin?tab=locations&env=${data.Name}`} target="_blank" style={{ textDecoration: "none", }}>
                              <Tooltip title={"Click to configure this runtime location"} placement="top">
                                <Chip
                                  style={{ marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer", backgroundColor: red, }}
                                  label={"Stopped"}
                                  variant="outlined"
                                  color="secondary"
                                  onClick={(e) => {
                                    e.preventDefault()
                                    e.stopPropagation()
                                    window.open(`/admin?tab=locations&env=${data.Name}`, "_blank", "noopener,noreferrer")
                                  }}
                                />
                              </Tooltip>
                            </a>
                            :
                            <Chip
                              key={index}
                              style={{
                                color: green,
                                borderColor: green,
                                marginLeft: 0,
                                padding: 0, marginRight: 10,
                              }}
                              label={"Running"}
                              onClick={() => {
                              }}
                              variant="outlined"
                              color="primary"
                            />
                          }
                          {data.default === true ?
                            <Chip
                              style={{ marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer", color: theme.palette.text.primary, border: theme.palette.textFieldStyle.border, backgroundColor: theme.palette.slateGrayColor }}
                              label={"Default"}
                              variant="outlined"
                              color="secondary"
                            />
                            : null}
                          {data.Name}
                        </MenuItem>
                      );
                    })}
                  </Select>
                </FormControl>
              </Tooltip>
            : null}
  
            {workflowAsCode && (
            <Tooltip title="Switch to Code View">
              <Button 
                variant="contained"
                color="secondary"
                onClick={() => {
                  window.location.href = `/workflows/${workflow.id}/code`
                }}
                style={{
                  height: 40,
                  borderRadius: theme.palette?.borderRadius,
                  color: theme.palette.text.primary,
                  textTransform: "none",
                  pointerEvents: "auto",
                  width: "100%",
                }}
              >
                Build this workflow as code
              </Button>
            </Tooltip>
            )}
          </div>
        </div>
  
        {/* Warning Messages */}
        {!distributedFromParent || userdata?.support === true ?
          isCorrectOrg ? null : 
            <Typography variant="body2" style={{ marginLeft: 10, marginTop: 10 }}>
              <b>Warning</b>: <span
                style={{ color: "#FF8544", cursor: "pointer", pointerEvents: "auto", }}
                onClick={() => {
                  toast("Changing to correct organisation. Please wait a few seconds.")
                  changeOrg()
                }}
              >Change Active Organization</span> to edit this Workflow.
            </Typography>
          :
  
          suborgWorkflows?.length === 0 ? 
            <Typography variant="body2" color="textSecondary" style={{ marginLeft: 10, marginTop: 10 }}>
              <b>Warning:</b> This workflow is controlled by your parent org and may not be editable.
            </Typography>
            :
            null
        }
  
      </div>
  
      {parentWorkflows === undefined || parentWorkflows === null || parentWorkflows.length === 0 ? null :
        <div style={{ display: "flex", marginLeft: 40, maxWidth: 250, pointerEvents: "auto", marginTop: 5, }}>
          <Typography variant="body2" color="textSecondary" style={{ marginRight: 5, marginTop: 5, }}>
            <b>Parent Workflows:</b>
          </Typography>
          {parentWorkflows.slice(0, 5).map((wf, index) => {
            return (
              <a href={`/workflows/${wf.id}`} target="_blank" rel="noopener noreferrer" key={index}>
                <Tooltip arrow placement="left" title={
                  <span style={{}}>
                    {wf.image !== undefined && wf.image !== null && wf.image.length > 0 ?
                      <img
                        src={wf.image}
                        alt={wf.name}
                        style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette?.borderRadius, }}
  
                      />
                      : null}
                    <Typography>
                      Parent workflow: '{wf.name}'
                    </Typography>
                  </span>
  
                }>
                  <span onClick={() => {
                    console.log("Click: ", wf)
                  }}>
                    <img src={theme.palette.defaultImage} style={{ height: 25, width: 25, cursor: "pointer", border: 15, marginRight: 5, marginTop: 5, filter: "grayscale(90%)", }} />
                  </span>
                </Tooltip>
              </a>
            )
          })}
        </div>
      }
    </div>
    );
  };

  const WorkflowMenu = () => {
    const [newAnchor, setNewAnchor] = React.useState(null);
    const [showShuffleMenu, setShowShuffleMenu] = React.useState(false);

    return (
      <div style={{ display: "inline-block" }}>
        <Menu
          id="long-menu"
          anchorEl={newAnchor}
          open={showShuffleMenu}
          onClose={() => {
            setShowShuffleMenu(false);
          }}
        >
          <div
            style={{ margin: 15, color: theme.palette.text.primary, maxWidth: 250, minWidth: 250 }}
          >
            <h4>This menu is used to control the workflow itself.</h4>
            <Divider
              style={{
                backgroundColor: theme.palette.text.primary,
                marginTop: 10,
                marginBottom: 10,
              }}
            />

            <FormControlLabel
              style={{ marginBottom: 15, color: theme.palette.text.primary }}
              label={<div style={{ color: theme.palette.text.primary }}>Skip Notifications</div>}
              control={
                <Switch
                  checked={workflow.configuration.skip_notifications}
                  onChange={() => {
                    workflow.configuration.skip_notifications =
                      !workflow.configuration.skip_notifications;
                    setWorkflow(workflow);
                    setUpdate(
                      "skip_notifications" +
                        workflow.configuration.skip_notification
                        ? "true"
                        : "false"
                    );
                  }}
                />
              }
            />
            <FormControlLabel
              style={{ marginBottom: 15, color: theme.palette.text.primary }}
              label={<div style={{ color: theme.palette.text.primary }}>Exit on Error</div>}
              control={
                <Switch
                  checked={workflow.configuration.exit_on_error}
                  onChange={() => {
                    workflow.configuration.exit_on_error =
                      !workflow.configuration.exit_on_error;
                    setWorkflow(workflow);
                    setUpdate(
                      "exit_on_error_" + workflow.configuration.exit_on_error
                        ? "true"
                        : "false"
                    );
                  }}
                />
              }
            />
            <FormControlLabel
              style={{ marginBottom: 15, color: theme.palette.text.primary }}
              label={<div style={{ color: theme.palette.text.primary }}>Start from top</div>}
              control={
                <Switch
                  checked={workflow.configuration.start_from_top}
                  onChange={() => {
                    workflow.configuration.start_from_top =
                      !workflow.configuration.start_from_top;
                    setWorkflow(workflow);
                    setUpdate(
                      "start_from_top_" + workflow.configuration.start_from_top
                        ? "true"
                        : "false"
                    );
                  }}
                />
              }
            />
          </div>
        </Menu>
        {/*
        <Tooltip
          color="secondary"
          title="Workflow settings"
          placement="top-start"
        >
          <span>
            <Button
              disabled={workflow.public}
              color="primary"
              style={{ height: 50, marginLeft: 10 }}
              variant="outlined"
              onClick={(event) => {
                setShowShuffleMenu(!showShuffleMenu);
                setNewAnchor(event.currentTarget);
              }}
            >
              <SettingsIcon />
            </Button>
          </span>
        </Tooltip>
				*/}
        {isMobile ?
          <Tooltip
            color="secondary"
            title="Show apps"
            placement="top-start"
          >
            <span>
              <Button
                disabled={workflow.public}
                color="primary"
                style={{ height: 50, marginLeft: 10 }}
                variant="outlined"
                onClick={(event) => {
                  console.log("Show apps!")
                  setLeftBarSize(leftViewOpen ? 0 : 60)
                  setLeftViewOpen(!leftViewOpen)
                }}
              >
                <AppsIcon />
              </Button>
            </span>
          </Tooltip>
          : null}
      </div>
    );
  };

  const handleActionHover = (inside, actionId) => {
    if (cy !== undefined && cy !== null) {
      var node = cy.getElementById(actionId);
      if (node.length > 0) {
        if (inside) {
          node.addClass("shuffle-hover-highlight");
        } else {
          node.removeClass("shuffle-hover-highlight");
        }
      }
    }
  }

  const handleHistoryUndo = () => {
    //console.log("history: ", history, "index: ", historyIndex);
    var item = history[historyIndex - 1];
    if (historyIndex === 0) {
      item = history[historyIndex];
    }

    if (item === undefined) {
      console.log("Couldn't find the action you're looking for");
      return;
    }

    //console.log("HANDLE: ", item);
    if (item.type === "node" && item.action === "removed") {
      // Re-add the node
      console.log("Item: ", item.data)

      const edge = cy.getElementById(item.data.id).json()
      if (edge !== null && edge !== undefined) {
        console.log("Couldn't add node as it exists")
        return
      }

      cy.add({
        group: "nodes",
        data: item.data,
        position: item.data.position,
      });
    } else if (item.action === "added") {
      //console.log("Should remove item!");
      const currentitem = cy.getElementById(item.data.id);
      if (currentitem !== undefined && currentitem !== null) {
        currentitem.remove();
      }

    } else if (item.type === "edge" && item.action === "removed") {
      const sourcenode = cy.getElementById(item.data.source)
      const targetnode = cy.getElementById(item.data.target)
      if (sourcenode === undefined || sourcenode === null || targetnode === undefined || targetnode === null) {
        console.log("Can't readd bad edge!")
        return
      }

      const edge = cy.getElementById(item.data.id).json()
      if (edge !== null && edge !== undefined) {
        console.log("Couldn't add edge as it exists")
        return
      }

      cy.add({
        group: "edges",
        data: item.data,
      });

    } else {
      console.log("UNHANDLED: ", item)
    }

    if (historyIndex > 0) {
      setHistoryIndex(historyIndex - 1);
    }
  };

  const BottomAvatars = () => {
    const connectedUsers = [{
      "user": "Anonymous",
      "user_id": "user_id",
      "color": "blue",
    }]


    if (connectedUsers === undefined || connectedUsers === null || connectedUsers.length < 2) {
      return null
    }

    const avatarStyle = {
      position: "fixed",
      display: "flex",
      right: isMobile ? 20 : 20,
      top: isMobile ? appBarSize - 100 : undefined,
      bottom: isMobile ? undefined : 0,
      left: isMobile ? undefined : leftBarSize,
      minWidth: cytoscapeViewWidths,
      maxWidth: cytoscapeViewWidths,
      marginLeft: 20,
      marginBottom: 20,
      zIndex: 50,
    }

    const HandleAvatar = (props) => {
      const { user } = props
      console.log("Clicked avatar: ", user)

      const userTitle = user.user[0].toUpperCase()
      return (
        <Tooltip title={user.user} placement="top">
          <Avatar style={{
            borderColor: user.color,
            marginLeft: 5,
            marginRight: 5,
            borderWidth: 3,
          }}>
            {userTitle}
          </Avatar>
        </Tooltip>
      )
    }

    return (
      <div style={avatarStyle}>
        {connectedUsers.map((user) => {
          return (
            <HandleAvatar user={user} />
          )
        })}
      </div>
    )
  }

  const shownErrors = !isMobile && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors && (!workflow.public || userdata.support === true) ?
    <div
    style={{
      border: theme.palette.DialogStyle.border,
      position: "absolute",
      bottom: 100,
      left: leftSideBarOpenByClick ? leftBarSize + 270 : leftBarSize + 115,
      width: "fit-content",
      maxWidth: "45vw",
      minWidth: 300,

        color: theme.palette.DialogStyle.color,
        padding: 10,
        borderRadius: theme.palette?.borderRadius,
        transition: "left 0.3s ease, top 0.3s ease",
      overflowWrap: "anywhere",
      wordBreak: "break-word",
      whiteSpace: "pre-wrap",
    }}
    >

      <Tooltip
        title="Hides error messages this session."
        placement="top"
      >
        <IconButton
          style={{ position: "absolute", top: 0, right: 0 }}
          sx={{
            "&:hover": {
              backgroundColor: theme.palette.hoverColor,
            },
          }}
          onClick={(e) => {
            e.preventDefault();

            // A temporary hider thing
            setShowErrors(false)
          }}
        >
          <CloseIcon style={{ color: theme.palette.text.primary }} />
        </IconButton>
      </Tooltip>

      <Typography variant="body2" color={theme.palette.textColor}>
        {/*<WarningIcon style={{marginRight: 5, height: 15, width: 15, }} />*/}
        <b>{workflow.errors.length} Workflow Issue{workflow.errors.length > 1 ? "s" : ""}</b>
      </Typography>
      <Typography
        variant="body2"
        color={theme.palette.textColor}
      >
        {workflow.errors.slice(0, 3).map((error, index) => {
          // Loop through each word, and if it matches "Action <name> " then replace it with a link to the action
          var colornext = false
          const newerror = error === undefined || error == null ? "" : error.split(" ").map((word) => {
            if (colornext) {
              colornext = false
              return (
                <span
				  key={index}
                  style={{ color: "#FF8544", cursor: "pointer" }}
                  onClick={() => {
                    // Find it in cytoscape
                    if (cy === undefined || cy === null) {
                      return
                    }

                    const foundnode = cy.nodes().filter((node) => {
                      const nodelabel = node.data("label")
                      if (nodelabel === undefined || nodelabel === null) {
                        return false
                      }

                      return nodelabel.toLowerCase() === word.toLowerCase()
                    })

                    if (foundnode === undefined || foundnode === null || foundnode.length === 0) {
                      return
                    }

                    cy.elements().unselect()
                    foundnode[0].select()
                  }}
                >
                  {word}&nbsp;
                </span>
              )
            }

            if (word.toLowerCase() === "action") {
              colornext = true
            }

            return word + " "
          })

          if (newerror === undefined || newerror === null || newerror === "") {
            return null
          }

          return (
            <div>
              - {newerror}
            </div>
          )
        })}
      </Typography>
    </div>
    : null



  const RightsideBar = () => {
    const [hovered, setHovered] = useState(false)

    useEffect(() => {
      const handleKeyDown = (event) => {
        if ((event.metaKey || event.ctrlKey) && event.key === '/') {
          event.preventDefault(); // Prevent default browser behavior (like opening search bar)
          if (!workflow.public && !executionRequestStarted) {
            executeWorkflow(executionText, workflow.start, lastSaved);
          }
        }
        if ((event.ctrlKey || event.metaKey) && event.key === "'") {
          // Check if Ctrl (Windows/Linux) or Command (Mac) key is pressed along with '/'
          if (!workflow.public && !executionModalOpen) {
            setExecutionModalOpen(true);
            getWorkflowExecution(props.match.params.key, "");
          } else if (!workflow.public && executionModalOpen) {
            setExecutionModalOpen(false);
          }
        }

        if ((event.ctrlKey || event.metaKey) && event.key === "]") {
          console.log("Show workflow revisions key pressed")
          if (!workflow.public) {
            setShowWorkflowRevisions(true)
            //setOriginalWorkflow(workflow)
          }
        }

        if ((event.ctrlKey || event.metaKey) && event.key === ";") {
          if (!workflow.public && executionModalOpen) {
            getWorkflowExecution(props.match.params.key, "");
          }
        }

        /*
            if (( event.ctrlKey || event.metaKey ) && event.shiftKey) {
              console.log("Shift key pressed")
              if (!workflow.public && executionModalOpen) {
                setExecutionRunning(false);
                stop()
                const newitem = removeParam("execution_id", cursearch);
                navigate(curpath + newitem)
                setExecutionModalView(0);
              }
            }
        */
      };

      document.addEventListener('keydown', handleKeyDown);

      return () => {
        document.removeEventListener('keydown', handleKeyDown);
      }
    }, [executeWorkflow, executionText, workflow, lastSaved, executionRequestStarted])

    useEffect(() => {
      const handleKeyDown = (event) => {
        if ((event.metaKey || event.ctrlKey) && event.key === '/') {
          event.preventDefault(); // Prevent default browser behavior (like opening search bar)
          if (!workflow.public && !executionRequestStarted) {
            executeWorkflow(executionText, workflow.start, lastSaved);
          }
        }
        if ((event.ctrlKey || event.metaKey) && event.key === "'") {
          // Check if Ctrl (Windows/Linux) or Command (Mac) key is pressed along with '/'
          if (!workflow.public && !executionModalOpen) {
            setExecutionModalOpen(true);
            getWorkflowExecution(props.match.params.key, "");
          } else if (!workflow.public && executionModalOpen) {
            setExecutionModalOpen(false);
          }
        }

        if ((event.ctrlKey || event.metaKey) && event.key === "]") {
          console.log("Show workflow revisions key pressed")
          if (!workflow.public) {
            setShowWorkflowRevisions(true)
          }
        }

        if ((event.ctrlKey || event.metaKey) && event.key === ";") {
          if (!workflow.public && executionModalOpen) {
            getWorkflowExecution(props.match.params.key, "");
          }
        }

        /*
            if (( event.ctrlKey || event.metaKey ) && event.shiftKey) {
              console.log("Shift key pressed")
              if (!workflow.public && executionModalOpen) {
                setExecutionRunning(false);
                stop()
                const newitem = removeParam("execution_id", cursearch);
                navigate(curpath + newitem)
                setExecutionModalView(0);
              }
            }
        */
      };

      document.addEventListener('keydown', handleKeyDown);

      return () => {
        document.removeEventListener('keydown', handleKeyDown);
      };
    }, [executeWorkflow, executionText, workflow, lastSaved, executionRequestStarted]);

    if (isMobile) {
      return null
    }

    if (workflow.public === true) {
      return null
    }

    return (
      <div
        style={{
          position: "fixed",
          right: -5,
          top: "40%",
          width: 70,
          height: 235,
          border: "1px solid #FF8544",
          cursor: "pointer",
          borderRadius: theme.palette?.borderRadius,
          padding: 10,
          backgroundColor: hovered ? theme.palette.surfaceColor : theme.palette.platformColor,
        }}
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}
        onClick={() => {
          setExecutionModalOpen(true);
          getWorkflowExecution(workflow.id, "", executionFilter, workflow.org_id)
        }}
      >
        <ArrowLeftIcon style={{ marginTop: 10, marginLeft: 10, marginBottom: 10, }} />
        <Typography
          variant="h6"
          style={{
            writingMode: "vertical-rl",
            textOrientation: "mixed",
            marginLeft: 10,
            fontWeight: "bold",
          }}
        >
          Explore runs
        </Typography>
      </div>
    )
  }

  // Used for handling suborg workflow distribution management
  const updateCurrentWorkflow = (inputworkflow) => {

    setCurrentWorkflow(inputworkflow)

    //setLastSaved(false)
    setSelectedAction({});
    setSelectedApp({})
    setWorkflow(inputworkflow)

	if (inputworkflow.id === originalWorkflow.id) {
		if (selectedActionEnvironment !== undefined && selectedActionEnvironment !== null && selectedActionEnvironment.Name !== undefined && selectedActionEnvironment.Name !== null) {
			setOriginalSelectedEnvironment(selectedActionEnvironment)
		}
	} 

	if (inputworkflow.id === originalWorkflow.id && originalSelectedEnvironment !== undefined && originalSelectedEnvironment !== null && originalSelectedEnvironment.Name !== undefined && originalSelectedEnvironment.Name !== null) {
		setSelectedActionEnvironment(originalSelectedEnvironment)
	} else {
		//console.log("Checking input workflow actions for env: ", inputworkflow.actions)
		if (inputworkflow.actions !== undefined && inputworkflow.actions !== null && inputworkflow.actions.length > 0) {

			for (var actionkey in inputworkflow.actions) {
				const action = inputworkflow.actions[actionkey]
				if (action.environment === undefined || action.environment === null || action.environment === "") {
					continue
				}

				//const env = environments.find((a) => a.Name === action.environment)
				const newenv = {
					Name: action.environment,
					Type: action.environment === "cloud" ? "cloud" : "onprem",
				}

				setSelectedActionEnvironment(newenv)
				break
			}
		}
	}

    if (inputworkflow !== undefined && inputworkflow !== null && inputworkflow.id !== undefined && inputworkflow.id !== null) {
      getRevisionHistory(inputworkflow.id, 50, 0, inputworkflow.org_id)
      getWorkflowExecution(inputworkflow.id, "", executionFilter, inputworkflow.org_id)
	  loadTriggers(inputworkflow.org_id)
    }

    // Update props match key
    if (inputworkflow.parentorg_workflow !== undefined && inputworkflow.parentorg_workflow !== null && inputworkflow.parentorg_workflow !== "") {
      setDistributedFromParent(inputworkflow.parentorg_workflow)
    } else {
      setDistributedFromParent("")
    }

    if (cy !== undefined && cy !== null) {
      cy.removeListener("select");
      cy.removeListener("unselect");

      cy.removeListener("add");
      cy.removeListener("remove");

      cy.removeListener("mouseover");
      cy.removeListener("mouseout");

      cy.removeListener("drag");
      cy.removeListener("free");
      cy.removeListener("cxttap");


      // Remove all edges
      setElements([])
      cy.edges().remove()
      cy.nodes().remove()
    }
  }

  // Uses Org-Id referencing header to create a workflow while getting it in realtime
  // This further ensures the user needs access to GET the workflow properly
  const duplicateParentWorkflow = (inputWorkflow, org_id, setWorkflow) => {
    fetch(`${globalUrl}/api/v1/workflows/${inputWorkflow.id}`, {
      method: "GET",
      headers: {
        "Org-Id": org_id,
        "Content-Type": "application/json",
      },
      credentials: "include",
    })
      .then((response) => {
        if (response.status === 200) {
          getChildWorkflows(inputWorkflow.id)
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.success === false) {
          //toast("Failed to duplicate workflow")
        } else {
          //toast("Successfully duplicated workflow. Reloading child workflows.")
          if (setWorkflow === true) {
            updateCurrentWorkflow(responseJson)
          }
        }
      })
      .catch((error) => {
        console.log("Dupe workflow for suborg error: ", error.toString())
      })
  } 

  const BottomCytoscapeBar = () => {
    if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) {
      return null;
    }

    const saveIcon = (
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
        <path
          d="M13.8333 15.5H2.16667C1.72464 15.5 1.30072 15.3244 0.988155 15.0118C0.675595 14.6993 0.5 14.2754 0.5 13.8333V2.16667C0.5 1.72464 0.675595 1.30072 0.988155 0.988155C1.30072 0.675595 1.72464 0.5 2.16667 0.5H11.3333L15.5 4.66667V13.8333C15.5 14.2754 15.3244 14.6993 15.0118 15.0118C14.6993 15.3244 14.2754 15.5 13.8333 15.5Z M12.1673 15.5002V8.8335H3.83398V15.5002 M3.83398 0.5V4.66667H10.5007"
          stroke={!(lastSaved && !workflow.public) ? "#ff8544" : "#F1F1F1"}
      />
      </svg>
    );

    const toolbarHeight = 40;
    const buttonSize = 40;
    const borderRadius = "8px";

    const iconSvgSize = 21;

    // Play/Stop button
    const executionButton = executionRunning ? (
      <Tooltip color="primary" title="Stop execution" placement="top">
        <Button
          onClick={() => abortExecution()}
          sx={{
            minWidth: buttonSize,
            width: buttonSize,
            height: buttonSize,
            borderRadius: "8px",
            backgroundColor: "#FF8544",
            color: "#ffffff",
            padding: 0,
            "&:hover": {
              backgroundColor: "#FF955C",
            },
          }}
        >
          <PauseIcon sx={{ fontSize: 20 }} />
        </Button>
      </Tooltip>
    ) : (
      <Tooltip title="Execute workflow (Ctrl + /)" placement="top">
        <Button
          disabled={workflow.public || executionRequestStarted}
          onClick={() => executeWorkflow(executionText, workflow.start, lastSaved)}
          sx={{
            minWidth: buttonSize,
            width: buttonSize,
            height: buttonSize,
            borderRadius: "8px",
            backgroundColor: theme.palette.accentColor,
            color: theme.palette.text.primary,
            padding: 0,
            paddingLeft: 0.2,
            "&:hover": {
              backgroundColor: "#FF955C",
            },
            "&:disabled": {
              backgroundColor: "#494949",
              color: "#9E9E9E",
            },
          }}
        >
         <img src="/icons/workflow-page/playButton.svg" alt="Play" style={{ width: 15, height: 15 }} />
        </Button>
      </Tooltip>
    );

    return (
      <div style={bottomBarStyle}>
        <Box
          sx={{
            display: "flex",
            alignItems: "center",
            gap: 0,
            backgroundColor: theme.palette.platformColor,
            borderRadius: "12px",
            padding: "10px",
            border: theme.palette.defaultBorder,
            boxShadow: "0px 2px 8px rgba(0, 0, 0, 0.3)",
          }}
        >
          {/* Play Button */}
          {executionButton}

          {/* Execution History Button - was in ButtonGroup with executionButton in old code */}
          <Tooltip
            color="secondary"
            title={`Show previous workflow runs (${workflowExecutions.length}) (Ctrl + ')`}
            placement="top"
          >
            <Button
              disabled={workflow.public}
              color="secondary"
              variant={executionModalOpen ? "contained" : "text"}
              sx={{
                minWidth: buttonSize,
                width: buttonSize,
                height: buttonSize,
                borderRadius: borderRadius,
                marginLeft: "8px",
                padding: 0,
                "&:hover": {
                  backgroundColor: executionModalOpen ? "rgba(255, 255, 255, 0.1)" : "rgba(255, 255, 255, 0.05)",
                },
                "&:disabled": {
                  color: "#9E9E9E",
                },
              }}
              onClick={() => {
                setExecutionModalOpen(true);
                getWorkflowExecution(workflow.id, "", executionFilter, workflow.org_id);
              }}
            >
              <DirectionsRunIcon sx={{ fontSize: 20 }} />
            </Button>
          </Tooltip>
          <Tooltip
            color="primary"
            title="An argument to be used for execution. This is a variable available to every node in your workflow."
            placement="top"
          >
            <TextField
              id="execution_argument_input_field"
              variant="outlined"
              placeholder="Runtime Argument"
              defaultValue={executionText}
              disabled={isMobile || workflow.public || executionRunning}
              onBlur={(e) => setExecutionText(e.target.value)}
              sx={{
                minWidth: 160,
                maxHeight: 38,
                height: toolbarHeight,
                marginLeft: "8px",
                "& .MuiOutlinedInput-root": {
                  height: toolbarHeight,
                  borderRadius: borderRadius,
                  backgroundColor: theme.palette.backgroundColor,
                  color: theme.palette.text.primary,
                  maxHeight: 38,
                  "& fieldset": {
                    borderColor: "transparent",
                  },
                  "&:hover fieldset": {
                    borderColor: "transparent",
                  },
                  "&.Mui-focused fieldset": {
                    borderColor: "transparent",
                  },
                  "&.Mui-disabled": {
                    backgroundColor: "#212121",
                    color: "#9E9E9E",
                  },
                },
                "& .MuiInputBase-input": {
                  padding: "0 12px",
                  maxHeight: 38,
                  fontSize: 14,
                  "&::placeholder": {
                    color: theme.palette.text.primary,
                    opacity: 0.7,
                    paddingLeft: 1,
                  },
                },
              }}
            />
          </Tooltip>

        {/*userdata.avatar === creatorProfile.github_avatar ? null :*/}

          <Tooltip title={workflow.public === true ? "Use this Workflow in your organisation" : "Save Workflow"} placement="top">
            <span>
            <Button
              disabled={savingState !== 0}
              onClick={() => {
                saveWorkflow(workflow)
                if (workflow.public === true) {
                  console.log("Public!");
                  const tmpurl = new URL(window.location.href);
                  const searchParams = tmpurl.searchParams;
                  const queryID = searchParams.get('queryID');
                  if (queryID !== undefined && queryID !== null) {
                    aa('init', {
                      appId: "JNSS5CFDZZ",
                      apiKey: "c8f882473ff42d41158430be09ec2b4e",
                    })
                    const timestamp = new Date().getTime();
                    aa('sendEvents', [
                      {
                      eventType: 'conversion',
                      eventName: 'Public Workflow Saved',
                      index: 'workflows',
                      objectIDs: [workflow.id],
                      timestamp: timestamp,
                      queryID: queryID,
                      userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
                    }
                  ])
                  }
                } else {
                  console.log("No query to handle")
                }
              }}
              sx={{
                height: toolbarHeight,
                borderRadius: borderRadius,
                fontFamily: theme?.typography?.fontFamily,
                backgroundColor: !(lastSaved && !workflow.public) ? "rgba(255, 133, 68, 0.2)" : theme.palette.slateGrayColor,
                color: "#ffffff",
                fontSize: "12px",
                textTransform: "none",
                padding: "0px 14px",
                marginLeft: "8px",
                maxHeight: 35,
                display: "flex",
                alignItems: "center",
                gap: "6px",
                "& .MuiTypography-root": {
                     fontSize: "14px",
                     color: !(lastSaved && !workflow.public) ? "#ff8544" : theme.palette.text.primary
                  },
                "& .MuiSvgIcon-root": {
                     color: !(lastSaved && !workflow.public) ? "#ff8544" : theme.palette.text.primary
                },
                "&:hover": {
                  backgroundColor: "rgba(255, 133, 68, 0.2)",
                  border: "0.5px solid #ff8544",
                },
                "&:disabled": {
                  backgroundColor: "#494949",
                  color: "#9E9E9E",
                  "& .MuiSvgIcon-root": {
                    color: "#9E9E9E",
                  },
                  "& .MuiTypography-root": {
                    color: "#9E9E9E",
                  },
                },
              }}
            >
              {savingState === 2 ? (
                <CircularProgress size={16} thickness={4} sx={{ color: "#ffffff" }} />
              ) : savingState === 1 ? (
                <DoneIcon sx={{ fontSize: 18, color: green }} />
              ) : (
                <span style={{ marginTop: 4 }}>
                  {saveIcon} 
                </span>
              )}
              <Typography variant="body2" sx={{ fontSize: 14, color: "#ff8544" }}>
                Save
              </Typography>
            </Button>
            </span>
          </Tooltip>

          {/* Separator */}
          <Divider
            orientation="vertical"
            flexItem
            sx={{
              marginLeft: "8px",
              marginRight: "4px",
              borderColor: "rgba(255, 255, 255, 0.2)",
              height: 24,
              alignSelf: "center",
            }}
          />

          <Box sx={{ display: "flex", alignItems: "center", gap: 0, marginLeft: "4px" }}>
            {/* Download */}
              <Tooltip
                color="secondary"
                title="Download workflow"
                placement="top"
              >
                <IconButton
                  onClick={() => {
                    const data = workflow;
                    let exportFileDefaultName = data.name + ".json";
                    let dataStr = JSON.stringify(data);
                    let dataUri =
                    "data:application/json;charset=utf-8," + encodeURIComponent(dataStr);
                    let linkElement = document.createElement("a");
                    linkElement.setAttribute("href", dataUri);
                    linkElement.setAttribute("download", exportFileDefaultName);
                    linkElement.click();
                  }}
                  sx={{
                    width: toolbarHeight,
                    height: toolbarHeight,
                    color: theme.palette.text.primary,
                    "&:hover": {
                      backgroundColor: "rgba(255, 255, 255, 0.05)",
                    },
                  }}
                >
                  <img
                    src={themeMode === "dark" ? "/icons/workflow-page/download.svg" : "/icons/workflow-page/download-dark.svg"}
                    alt="Download"
                    style={{ width: iconSvgSize, height: iconSvgSize }}
                    draggable={false}
                  />
                </IconButton>
              </Tooltip>

            {/* Undo */}
            <Tooltip color="secondary" title="Undo" placement="top">
              <IconButton
                disabled={history.length === 0 || !(originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0 || originalWorkflow.suborg_distribution.includes("none"))}
                onClick={(event) => handleHistoryUndo(history)}
                sx={{
                  width: toolbarHeight,
                  height: toolbarHeight,
                  color: theme.palette.text.primary,
                  "&:hover": {
                    backgroundColor: "rgba(255, 255, 255, 0.05)",
                  },
                  "&:disabled": {
                    color: "#9E9E9E",
                  },
                }}
              >
                <img 
                  src={themeMode === "dark" ? "/icons/workflow-page/undo.svg" : "/icons/workflow-page/undo-dark.svg"}
                  alt="Undo" 
                  style={{ 
                    width: iconSvgSize, 
                    height: iconSvgSize, 
                    opacity: (history.length === 0 || !(originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0 || originalWorkflow.suborg_distribution.includes("none"))) ? 0.5 : 1 
                  }}
                  draggable={false} 
                />
              </IconButton>
            </Tooltip>

            {/* Fit to Screen */}
            <Tooltip color="secondary" title="Fit to screen" placement="top">
              <IconButton
                onClick={() => cy.fit(null, 50)}
                sx={{
                  width: toolbarHeight,
                  height: toolbarHeight,
                  color: theme.palette.text.primary,
                  "&:hover": {
                    backgroundColor: "rgba(255, 255, 255, 0.05)",
                  },
                }}
              >
                <img 
                  src={themeMode === "dark" ? "/icons/workflow-page/fitToScreen.svg" : "/icons/workflow-page/fitToScreen-dark.svg"}
                  alt="Fit to Screen" 
                  style={{ 
                    width: iconSvgSize, 
                    height: iconSvgSize, 
                  }} 
                  draggable={false}
                />
              </IconButton>
            </Tooltip>

            {/* Delete/Trash */}
            <Tooltip
              color="secondary"
              title="Remove selected item (del)"
              placement="top"
            >
              <IconButton
                disabled={workflow.public}
                onClick={() => {
                  const selectedNode = cy.$(":selected");
                  if (selectedNode.data() === undefined) {
                    return;
                  }
                  removeNode(selectedNode.data("id"));
                }}
                sx={{
                  width: toolbarHeight,
                  height: toolbarHeight,
                  color: theme.palette.text.primary,
                  "&:hover": {
                    backgroundColor: "rgba(255, 255, 255, 0.05)",
                  },
                  "&:disabled": {
                    color: "#9E9E9E",
                  },
                }}
              >
                <img
                  src={themeMode === "dark" ? "/icons/workflow-page/deleteIcon.svg" : "/icons/workflow-page/delete-dark.svg"}
                  alt="Delete"
                  style={{ width: iconSvgSize, height: iconSvgSize }}
                  draggable={false}
                />
              </IconButton>
            </Tooltip>

            {/* Separator */}
            <Divider
              orientation="vertical"
              flexItem
              sx={{
                marginLeft: "4px",
                marginRight: "4px",
                borderColor: "rgba(255, 255, 255, 0.2)",
                height: 24,
                alignSelf: "center",
              }}
            />

            {/* History */}
            <Tooltip
              color="secondary"
              title="Show Workflow Revision History (Ctrl + ])"
              placement="top"
            >
              <IconButton
                disabled={workflow.public}
                onClick={() => setShowWorkflowRevisions(true)}
                sx={{
                  width: toolbarHeight,
                  height: toolbarHeight,
                  color: theme.palette.text.primary,
                  "&:hover": {
                    backgroundColor: "rgba(255, 255, 255, 0.05)",
                  },
                  "&:disabled": {
                    color: "#9E9E9E",
                  },
                }}
              >
                <img
                  src={themeMode === "dark" ? "/icons/workflow-page/historyClock.svg" : "/icons/workflow-page/historyClock-dark.svg"}
                  alt="History"
                  style={{ width: iconSvgSize, height: iconSvgSize }}
                />
              </IconButton>
            </Tooltip>

            {/* Add */}
            <Tooltip
              color="secondary"
              title="Add comment"
              placement="top"
            >
              <IconButton
                disabled={workflow.public}
                onClick={() => addCommentNode()}
                sx={{
                  width: toolbarHeight,
                  height: toolbarHeight,
                  color: theme.palette.text.primary,
                  "&:hover": {
                    backgroundColor: "rgba(255, 255, 255, 0.05)",
                  },
                  "&:disabled": {
                    color: "#9E9E9E",
                  },
                }}
              >
                <img
                  src={themeMode === "dark" ? "/icons/workflow-page/comment.svg" : "/icons/workflow-page/comment-dark.svg"}
                  alt="Add Comment"
                  style={{ width: iconSvgSize, height: iconSvgSize }}
                  draggable={false}
                />
              </IconButton>
            </Tooltip>

            {/* WorkflowMenu - was conditionally rendered in old code */}
            {workflow.configuration !== null &&
              workflow.configuration !== undefined &&
              workflow.configuration.exit_on_error !== undefined ? (
              <WorkflowMenu />
            ) : null}

            {/*
          <Tooltip
            color="secondary"
            title="Edit workflow details"
            placement="top"
          >
            <span>
              <Button
                disabled={workflow.public}
                color="secondary"
			  	variant="text"
                style={{ width: 65, height: buttonHeights, }}
                onClick={() => {
                  console.log("SHOW EDIT VIEW!")

                  setEditWorkflowModalOpen(true)
  				  setLastSaved(false)
                }}
              >
                <EditIcon />
              </Button>
            </span>
          </Tooltip> 
          */}

            {userdata.support === true && (
             <Divider
                orientation="vertical"
                flexItem
                sx={{
                  marginLeft: "4px",
                  marginRight: "4px",
                  borderColor: "rgba(255, 255, 255, 0.2)",
                  height: 24,
                  alignSelf: "center",
                }}
              /> 
            )}

            {userdata?.support === true && (
              <Tooltip
                color="secondary"
                title="Show simplified YAML (support only)"
                placement="top"
              >
                <IconButton
                  disabled={workflow.public}
                  onClick={() => setupWorkflowYaml(workflow)}
                  sx={{
                    width: toolbarHeight,
                    height: toolbarHeight,
                    color: theme.palette.text.primary,
                    "&:hover": {
                      backgroundColor: "rgba(255, 255, 255, 0.05)",
                    },
                    "&:disabled": {
                      color: "#9E9E9E",
                    },
                  }}
                >
                  <img
                    src={themeMode === "dark" ? "/icons/workflow-page/yaml.svg" : "/icons/workflow-page/yaml-dark.svg"}
                    alt="YAML"
                    style={{ width: iconSvgSize, height: iconSvgSize }}
                    draggable={false}
                  />
                </IconButton>
              </Tooltip>
            )}
          </Box>

          {/* AI Button with Gradient */}
          {(userdata.support == true || !isCloud) && (
            <Tooltip
              color="secondary"
              title="Generate workflow (requires LLM model)"
              placement="top"
            >
              <Button
                onClick={() => setWorkflowGenerationModalOpen(!workflowGenerationModalOpen)}
                variant="aiButton"
                color="secondary"
                sx={{
                  minWidth: buttonSize,
                  width: buttonSize,
                  borderRadius: "8px",
                  height: buttonSize,
                  marginLeft: "4px",
                }}
              >
                <AutoAwesomeIcon sx={{ fontSize: 20 }} />
              </Button>
            </Tooltip>
          )}
        </Box>
      </div>
    );
  };

  const setupResizeHandlers = (cy, nodeId) => {
    let height;
    let width;
    let resizeTimeout;

    cy.on("drag", ".resize-handle", (event) => {
      if (resizeTimeout) return;

      resizeTimeout = setTimeout(() => {
        resizeTimeout = null;
        const handle = event.target;
        const parent = cy.$(`#${nodeId}`); // Fetch the main node directly

        // Check if the parent node exists
        if (!parent || parent.empty()) {
          console.warn(`Parent node (${nodeId}) not found.`);
          return;
        }

        if (!handle?.position()) {
          console.warn(`Handle position is undefined for ${handle.id()}`);
          return;
        }

        const handlePos = handle.position();
        const parentPos = parent.position();

        if (!parentPos) {
          console.warn(`Parent position is undefined for ${nodeId}`);
          return;
        }

        // Calculate new width & height based on handle movement
        const newWidth = Math.abs(handlePos.x - parentPos.x) * 2;
        const newHeight = Math.abs(handlePos.y - parentPos.y) * 2;

        // Apply min/max constraints
        const constrainedWidth = Math.max(100, Math.min(newWidth, 500));
        const constrainedHeight = Math.max(50, Math.min(newHeight, 300));

        // Update node size
        parent.style({
          width: constrainedWidth,
          height: constrainedHeight,
        });

        // Store dimensions for state update on drag end
        height = Math.floor(constrainedHeight);
        width = Math.floor(constrainedWidth);

        // Update handle positions
        cy.$(".resize-handle").forEach((corner) => {
          if (!corner?.id() || !corner.position()) return;

          const { x, y } = parent.position();
          const offsetX = corner.id().includes("left") ? -constrainedWidth / 2 : constrainedWidth / 2;
          const offsetY = corner.id().includes("top") ? -constrainedHeight / 2 : constrainedHeight / 2;

          corner.position({ x: x + offsetX, y: y + offsetY });
        });
      }, 16); // Throttle to ~60 FPS
    });

    // Update state when resizing ends
    cy.on("free", ".resize-handle", (event) => {
      const data = event.target.data();
      const parentNode = cy.getElementById(data.attachedTo);

      if (parentNode) {

        parentNode.data({
          ...parentNode.data(),
          width: Math.floor(width),
          height: Math.floor(height),
        });
        setSelectedComment((prev) => ({
          ...prev,
          width: Math.floor(width),
          height: Math.floor(height),
        }));
      }
    });

  };


  const setupNodeDragHandler = (cy, nodeId) => {
    cy.on("drag", `#${nodeId}`, (event) => {
      const node = event.target;
      const { x, y } = node.position();
      const width = parseFloat(node.style("width"));
      const height = parseFloat(node.style("height"));

      // Move resize handles with the node
      cy.$(".resize-handle").forEach((corner) => {
        const offsetX = corner.id().includes("left") ? -width / 2 : width / 2;
        const offsetY = corner.id().includes("top") ? -height / 2 : height / 2;

        corner.position({ x: x + offsetX, y: y + offsetY });
      });
    });
  };

  const addCommentNode = () => {
    const newId = uuidv4();
    const position = { x: 300, y: 300 };
    const width = 250;
    const height = 150;
    const handleOffset = 10; // Move handles outside the node

    cy.add({
      group: "nodes",
      data: {
        id: newId,
        label: "Click to write a comment",
        type: "COMMENT",
        is_valid: true,
        decorator: true,
        width,
        height,
        position,
        backgroundcolor: "#1f2023",
        color: "#ffffff",

        textHalign: "right",
        textValign: "bottom",
        textMarginX: "-250px",
        textMarginY: "-150px",
      },
      position,
    });

    // Define corner positions relative to the main node
    const corners = [
      { id: `${newId}-top-left`, dx: -width / 2 - handleOffset, dy: -height / 2 - handleOffset },
      { id: `${newId}-top-right`, dx: width / 2 + handleOffset, dy: -height / 2 - handleOffset },
      { id: `${newId}-bottom-left`, dx: -width / 2 - handleOffset, dy: height / 2 + handleOffset },
      { id: `${newId}-bottom-right`, dx: width / 2 + handleOffset, dy: height / 2 + handleOffset },
    ];

    // Add resize handles **without** the parent property
    corners.forEach((corner) => {
      cy.add({
        group: "nodes",
        data: {
          id: corner.id,
          type: "RESIZE-HANDLE",
          is_valid: true,
          attachedTo: newId,
          decorator: true,
        }, // No parent to avoid edges
        position: { x: position.x + corner.dx, y: position.y + corner.dy },
        classes: "resize-handle",
      });
    });

    setupResizeHandlers(cy, newId);
    setupNodeDragHandler(cy, newId);
  };

  const RightSideBar = (props) => {

    var defaultReturn = null
    if (Object.getOwnPropertyNames(selectedComment).length > 0 && selectedComment.hasOwnProperty('id')) {
      defaultReturn = <CommentSidebar />
    } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
      if (selectedTrigger.trigger_type === undefined) {
        // defaultReturn = <UserinputSidebar />
        return null;
      } else {
        /*
            console.log(
              "Unable to handle invalid trigger type " +
              selectedTrigger.trigger_type
            );
        */
        return null;
      }
    } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
      defaultReturn = <EdgeSidebar />
    }

    const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);

    const drawerBleeding = 56;
    if (defaultReturn === undefined || defaultReturn === null) {
      return null
    }

    return (
      isMobile ?
        <SwipeableDrawer
          anchor={"bottom"}
          disableBackdropTransition={!iOS}
          disableDiscovery={iOS}
          open={true}
          onClose={() => {
            console.log("Close!")
            //setRightSideBarOpen(false)
            cy.elements().unselect()
          }}
          disableSwipeToOpen={false}
          ModalProps={{
            keepMounted: true,
          }}
          PaperProps={{
            style: {
              maxHeight: "70%",
              overflow: "auto",
            }
          }}
        >
          {defaultReturn}
        </SwipeableDrawer>
        :
        <span>
          {/*<Fade in={true} style={{ transitionDelay: `$0ms` }}>*/}
          <div id="rightside_actions" style={rightsidebarStyle}>
            {defaultReturn}
          </div>
        </span>
    );

    //return null;
  };

  const unPublishWorkflow = (data) => {
    data.id = props.match.params.key
    if (!isCloud) {
      toast("Function only supported on cloud")
      return
    }

    if (data.public !== true) {
      toast("Workflow is not public. Can't unpublish");
      return
    }

    // This ALWAYS talks to Shuffle cloud
    data = JSON.parse(JSON.stringify(data));
    const url = `${globalUrl}/api/v1/workflows/${props.match.params.key}/unpublish`;
    fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(data),
      credentials: "include",
    })
      .then((response) => {
        if (response.status !== 200) {
          console.log("Status not 200 for workflow publish :O!");
        }

        return response.json();
      })
      .then((responseJson) => {
        if (responseJson.reason !== undefined) {
          toast("Unpublishing: " + responseJson.reason)
        }

        if (responseJson.success === true) {
          workflow.public = false
          setWorkflow(workflow)
        }
      })
      .catch((error) => {
        toast("Failed publishing: is the workflow valid? Remember to save the workflow first.")
        console.log(error.toString())
      })
  }

  // This can execute a workflow with firestore. Used for test, as datastore is old and stuff
  // Too much work to move everything over alone, so won't touch it for now
  //<Button style={{borderRadius: "0px"}} color="primary" variant="contained" onClick={() => {
  //	executeWorkflowWebsocket()
  //}}>Execute websocket</Button>
  //

  // A list used for FRONTEND handling of whether a public workflow
  // should be change-able
  const allowList = ["frikky", "m1nk-code", "DavidtheGoliath"]
  // console.log(allowList, userdata.public_username)

  const leftView = workflow.public === true ?
    <div style={{ minHeight: "82vh", maxHeight: "82vh", height: "100%", minWidth: leftBarSize - 70, maxWidth: leftBarSize - 70, zIndex: 0, padding: 35, borderRight: "1px solid rgba(91,96,100,1)", overflowY: "auto", }}>

      <span style={{ display: "flex", }}>
        <Typography variant="h6" color="textPrimary" style={{
          margin: "0px 0px 0px 0px",
        }}>
          {workflow.name}
        </Typography>
        {workflow.validated === true ?
          <Tooltip title="The functionality of this workflow manually verified by the Shuffle automation team" placement="top">
            <VerifiedUserIcon style={{ marginLeft: 10, }} />
          </Tooltip>
          : null}
      </span>
      <Typography variant="body2" color="textSecondary">
        This workflow is public	and <span style={{ color: "#f86a3e", cursor: "pointer", }} onClick={() => {
          saveWorkflow(workflow)
        }}>must be saved</span> or exported before use.
      </Typography>
      {Object.getOwnPropertyNames(creatorProfile).length !== 0 && creatorProfile.github_avatar !== undefined && creatorProfile.github_avatar !== null ?
        <div style={{ display: "flex", marginTop: 10, }}>
          <IconButton color="primary" style={{ padding: 0, marginRight: 10, }} aria-controls="simple-menu" aria-haspopup="true" onClick={(event) => {
          }}>
            <Link to={`/creators/${creatorProfile.github_username}`} style={{ textDecoration: "none", color: "#f86a3e" }}>
              <Avatar style={{ height: 30, width: 30, }} alt={"Workflow creator"} src={creatorProfile.github_avatar} />
            </Link>
          </IconButton>
          <Typography variant="body1" color="textSecondary" style={{ color: "" }}>
            Shared by <Link to={`/creators/${creatorProfile.github_username}`} style={{ textDecoration: "none", color: "#f86a3e" }}>{creatorProfile.github_username}</Link>
          </Typography>
        </div>
        : null}
      <div style={{ marginTop: 15 }} />
      {workflow.tags !== undefined && workflow.tags !== null && workflow.tags.length > 0 ?
        <div style={{ display: "flex", overflow: "hidden", marginTop: 5, }}>
          <Typography variant="body1" style={{ marginRight: 10, }}>
            Tags
          </Typography>
          <div style={{ display: "flex", flexWrap: 'wrap', gap: 5, }}>
            {workflow.tags.map((tag, index) => {
              if (index >= 3) {
                return null;
              }

              return (
                <Chip
                  key={index}
                  style={{ backgroundColor: theme?.palette?.chipStyle?.backgroundColor, height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, cursor: "pointer", borderColor: theme?.palette?.chipStyle?.borderColor, color: theme.palette.text.primary, }}
                  label={tag}
                  variant="outlined"
                  color="primary"
                />
              );
            })}
          </div>
        </div>
        : null}

      {workflow.blogpost !== undefined && workflow.blogpost !== null && workflow.blogpost.length > 0 ?
        <div style={{
          marginTop: 10,
          maxWidth: "100%",
          overflow: "hidden",
        }}>
          <Typography variant="body1">
            <a
              href={workflow.blogpost}
              style={{ textDecoration: "none", color: theme.palette.linkColor}}
              rel="noopener noreferrer"
              target="_blank"
            >
              Related blog & docs
            </a>
          </Typography>
        </div>
        : null
      }

      {appGroup.length > 0 ?
        <div style={{ marginTop: 10, }}>
          <div style={{ display: "flex" }}>
            <Typography variant="body1">
              Apps
            </Typography>
            <AvatarGroup max={6} style={{ marginLeft: 10, }}>
              {appGroup.map((data, index) => {
                return (
                  <Link key={index} to={`/apps/${data.app_id}`}>
                    <Avatar alt={data.app_name} src={data.large_image} style={{ width: 30, height: 30 }} />
                  </Link>
                )
              })}
            </AvatarGroup>
          </div>
        </div>
        : null}

      {triggerGroup.length > 0 ?
        <div style={{ display: "flex", marginTop: 10, }}>
          <Typography variant="body1">
            Triggers
          </Typography>
          <AvatarGroup max={6} style={{ marginLeft: 10, }}>
            {triggerGroup.map((data, index) => {
              return (
                <Avatar key={index} alt={data.app_name} src={data.large_image} style={{ width: 30, height: 30 }} />
              )
            })}
          </AvatarGroup>
        </div>
        : null}

      {/*
			<div style={{display: "flex", marginTop: 10, }}>
				<Typography variant="body1">
					Mitre Att&ck:&nbsp; 
				</Typography>
				<Typography variant="body1" color="textSecondary">
					TBD
				</Typography>
			</div>
			*/}

      {/*
			<div style={{display: "flex", marginTop: 10, }}>
				<Typography variant="body1">
					Related Workflows:
				</Typography>
				<Typography variant="body1" color="textSecondary">
					TBD
				</Typography>
			</div>
			*/}

      {workflow.video !== undefined && workflow.video !== null && workflow.video.length > 0 ?
        <div style={{
          marginTop: 10,
          maxWidth: "100%",
          overflow: "hidden",
        }}>
          <Typography variant="body1">
            Video
          </Typography>
          {
            workflow.video.includes("loom.com/share") && workflow.video.split("/").length > 4 ?
              <div>
                <iframe
                  src={`https://www.loom.com/embed/${workflow.video.split("/")[4]}`}
                  frameBorder={"false"}
                  webkitallowfullscreen={"true"}
                  mozallowFullscreen={true}
                  allowFullScreen={true}
                  style={{
                    "top": 0,
                    "left": 0,
                    "maxWidth": 270,
                    "minWidth": 270,
                  }}
                />
              </div>
              :
              workflow.video.includes("youtube.com") && workflow.video.split("/").length > 3 && workflow.video.includes("v=")
                ?
                <div>
                  <iframe
                    src={`https://www.youtube.com/embed/${((new URL(workflow.video)).searchParams).get("v")}`}
                    frameBorder={"false"}
                    webkitallowfullscreen={"true"}
                    mozallowFullscreen={true}
                    allowFullScreen={true}
                    style={{
                      "top": 0,
                      "left": 0,
                      "maxWidth": 270,
                      "minWidth": 270,
                    }}
                  />
                </div>
                :
                <Typography variant="body1">
                  {workflow.video}
                </Typography>
          }
        </div>
        : null}

      {workflow.description !== undefined && workflow.description !== null && workflow.description.length > 0 ?
        <div style={{ marginTop: 5, }}>
          <Typography variant="body1">
            Description
          </Typography>
          <Typography variant="body1" color="textSecondary" style={{ maxWidth: "100%", maxHeight: 250, overflowX: "hidden" }}>
            {workflow.description}
          </Typography>
        </div>
        : null}

      {/*
			<div style={{ display: "flex" }}>
				<Button
					color="primary"
					variant="contained"
					fullWidth
					style={{ marginTop: 15, display: "flex" }}
					onClick={() => {
						setSelectionOpen(true)
					}}
				>
					Statistics
				</Button>
			</div>
			*/}

      {userdata.support || 
      (userdata.avatar !== undefined && (userdata.avatar === creatorProfile.github_avatar || allowList.includes(userdata.public_username))) || 
       (workflow?.owner?.length > 0 && workflow.owner === userdata?.active_org?.id && userdata?.active_org.role === "admin") || 
       (workflow?.owner?.length > 0 && workflow.owner === userdata?.id)?
        <div style={{ marginTop: 50, }}>
          <Typography variant="body2" color="textSecondary">
            You can see these buttons because you may have the correct access rights as a creator to help modify this workflow.
          </Typography>
          <Button
            color="primary"
            fullWidth
            style={{ marginTop: 15, backgroundColor: "#ff8544", color: "#1a1a1a", fontSize: 16, textTransform: "none" }}
            onClick={() => {
              // Further checks are being done on the backend
              // Even if the user can "edit" a workflow on the frontend,
              // that doesn't necessarily mean anything

              //setEditWorkflowDetails(true)
              workflow.public = false
              setUserediting(true)
              setWorkflow(workflow)

              getAppAuthentication()
              getEnvironments(workflow.org_id)
              getAvailableWorkflows(-1)
              getFiles()

          	  getWorkflowExecution(workflow.id, "", executionFilter, workflow.org_id)

              // For loading datastore

              setUpdate(Math.random());
            }}
          >
            Edit Workflow
          </Button>
          <Button
            color="secondary"
            variant="outlined"
            disabled={workflow.public !== true}
            fullWidth
            style={{ marginTop: 15, textTransform: "none", fontSize: 16 }}
            onClick={() => {
              // setEditWorkflowDetails(true)
              // workflow.public = false
              // setWorkflow(workflow)
              // setUpdate(Math.random());
              toast("Unpublishing this workflow from the search engine. It will remain public until it is deleted, as it is a copy of the original workflow.")
              unPublishWorkflow(workflow)
            }}
          >
            Unpublish Workflow
          </Button>

          {userdata.support === true ?
            <span>
              <Typography variant="body2" color="textSecondary" style={{ marginTop: 50, }}>
                Manual Verification: <b>{workflow.validated === undefined || workflow.validated === null || workflow.validated === false ? "Not valided" : "Validated"}</b>
              </Typography>
              <div style={{ display: "flex", }}>
                <Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
                  Validate Workflow:
                </Typography>
                <Switch
                  value={workflow.validated === undefined || workflow.validated === null || workflow.validated === false ? false : workflow.validated}
                  onChange={(event) => {
                    workflow.validated = event.target.checked
                    workflow.user_editing = true
                    //setUserediting(true)

                    saveWorkflow(workflow)
                  }}
                />
              </div>
            </span>
            : null}
        </div>
        : null}


    </div>
    : isMobile && leftViewOpen ?
      <div
        style={{
          borderRight: "1px solid rgb(91, 96, 100)",
          transition: "width 0.3s ease, opacity 0.3s ease",
          overflow: "hidden",
        }}
      >
        <HandleLeftView />
      </div>
      : leftViewOpen ? (
        <div
          style={{
            minWidth: leftBarSize,
            maxWidth: leftBarSize,
            borderRight: "1px solid rgb(91, 96, 100)",
            transition: "min-width 0.3s ease, max-width 0.3s ease",
            overflow: "hidden",
          }}
        >
          <HandleLeftView />
        </div>
      ) : (
        <div
          style={{
            minWidth: leftBarSize,
            maxWidth: leftBarSize,
            borderRight: "1px solid rgb(91, 96, 100)",
            transition: "min-width 0.3s ease, max-width 0.3s ease",
            overflow: "hidden",
          }}
        >
          <div
            style={{ cursor: "pointer", height: 20, marginTop: 10, marginLeft: 10 }}
            onClick={() => {
              setLeftViewOpen(true);
              setLeftBarSize(350);
            }}
          >
            <Tooltip color="primary" title="Maximize" placement="top">
              <KeyboardArrowRightIcon />
            </Tooltip>
          </div>
        </div>
      );


  const parsedExecutionArgument = () => {
    var showResult = executionData.execution_argument.trim();
    const validate = validateJson(showResult);

    if (validate.valid) {
      if (typeof validate.result === "string") {
        try {
          validate.result = JSON.parse(validate.result);
        } catch (e) {
          console.log("Error: ", e);
          validate.valid = false;
        }
      }


      return (
        <div style={{ display: "flex", maxHeight: 500, overflow: "auto",}}>
          <IconButton
            style={{
              marginTop: "auto",
              marginBottom: "auto",
              height: 30,
              paddingLeft: 0,
              width: 30,
              color: theme.palette.text.primary,
            }}
            onClick={() => {
              if (validate.valid) {
                //console.log("Find and change result: ", 
                //const oldstartnode = cy.getElementById(selectedResult.action.id);
                //if (oldstartnode !== undefined && oldstartnode !== null) {
                //	const foundname = oldstartnode.data("label")
                //	if (foundname !== undefined && foundname !== null) {
                //		result.action.label = foundname
                //	}
                //}
              }

              setSelectedResult({
                "action": {
                  "label": "Runtime Argument",
                  "name": "Runtime Argument",
                  "large_image": theme.palette.defaultImage,
                  "image": theme.palette.defaultImage,
                },
                "result": validate.valid ? JSON.stringify(validate.result) : validate.result,
                "status": "SUCCESS"
              })

              setCodeModalOpen(true);
            }}
          >
            <Tooltip
              color="primary"
              title="Expand debug window"
              placement="top"
              style={{ zIndex: 10011 }}
            >
              <ArrowLeftIcon sx={{color: theme.palette.text.primary}} />
            </Tooltip>
          </IconButton>
          <ReactJson
            src={validate.result}
            theme={theme.palette.jsonTheme}
            style={theme.palette.reactJsonStyle}
            shouldCollapse={(jsonField) => {
              return collapseField(jsonField)
            }}
            iconStyle={theme.palette.jsonIconStyle}
            collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
            displayArrayKey={false}
            enableClipboard={(copy) => {
              handleReactJsonClipboard(copy);
            }}
            displayDataTypes={false}
            onSelect={(select) => {
              HandleJsonCopy(validate.result, select, "exec");
            }}
            name={"$exec"}
          />
        </div>
      )
    }

    return (
      <div>
        <h3>Runtime Argument</h3>
        <div style={{ maxHeight: 200, overflowY: "auto" }}>
          {executionData.execution_argument}
        </div>
      </div>
    );
  };

  const getExecutionSourceImage = (execution) => {

    // This is the playbutton at 150x150
    const defaultImage =
      "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg=="

    const size = isCloud ? 40 : 35;
    const borderRadius = 5
    if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) {
      return (
        <img
          alt="default"
          src={defaultImage}
          style={{
            width: size,
            height: size,
            borderRadius: borderRadius,
          }}
        />
      )
    }

    if (execution.execution_source?.startsWith("datastore")) {
        const iconMargin = 7
        return (
          <div style={{
            width: size,
            height: size,
            borderRadius: borderRadius,
            backgroundColor: green,
          }}>
            <StorageIcon
              style={{
                width: size / 3 * 2,
                height: size / 3 * 2,
                marginLeft: iconMargin,
                marginTop: iconMargin,
              }}
            />
          </div>
        )
	} else if (execution.execution_source === "authgroups") {
      const iconMargin = 7
      return (
        <div style={{
          width: size,
          height: size,
          borderRadius: borderRadius,
          backgroundColor: "rgba(222,112,72,1)",
        }}>
          <LockOpenIcon
            style={{
              width: size / 3 * 2,
              height: size / 3 * 2,
              marginLeft: iconMargin,
              marginTop: iconMargin,
            }}
          />
        </div>
      )

    } else if (execution.execution_source === "webhook") {
      return (
        <img
          alt={"webhook"}
          src={
            triggers.find((trigger) => trigger.trigger_type === "WEBHOOK")
              .large_image
          }
          style={{
            width: size,
            height: size,
            borderRadius: borderRadius,
          }}
        />
      );
    } else if (execution.execution_source === "outlook") {
      return (
        <img
          alt={"email"}
          src={
            triggers.find((trigger) => trigger.trigger_type === "EMAIL")
              .large_image
          }
          style={{
            width: size,
            height: size,
            borderRadius: borderRadius,
          }}
        />
      );
    } else if (execution.execution_source === "schedule") {
      return (
        <img
          alt={"schedule"}
          src={
            triggers.find((trigger) => trigger.trigger_type === "SCHEDULE")
              .large_image
          }
          style={{
            width: size,
            height: size,
            borderRadius: borderRadius,
          }}
        />
      );
    } else if (execution.execution_source === "EMAIL") {
      return (
        <img
          alt={"email"}
          src={
            triggers.find((trigger) => trigger.trigger_type === "EMAIL")
              .large_image
          }
          style={{
            width: size,
            height: size,
            borderRadius: borderRadius,
          }}
        />
      );
    } else if (execution.execution_source === "ShuffleGPT") {
      return (
        <AutoAwesomeIcon
          color="secondary"
          style={{ paddingTop: 8, paddingLeft: 4, height: 25, width: 25, }}
        />
      );
    } else if (execution.execution_source === "pipeline") {
      return (
        <img
          alt={"pipeline"}
          src={
            triggers.find((trigger) => trigger.trigger_type === "PIPELINE")
              .large_image
          }
          style={{ width: size, height: size }}
        />
      );
    }

    if (
      execution.execution_parent !== null &&
      execution.execution_parent !== undefined &&
      execution.execution_parent.length > 0
    ) {
      return (
        <img
          alt={"parent workflow"}
          src={
            triggers.find((trigger) => trigger.trigger_type === "SUBFLOW")
              .large_image
          }
          style={{
            width: size,
            height: size,
            borderRadius: borderRadius,
          }}
        />
      );
    }

    return (
      <img
        alt={execution.execution_source}
        src={defaultImage}
        style={{
          width: size,
          height: size,
          borderRadius: borderRadius,
        }}
      />
    );
  };

  // Not used because of issue with state updates.
  const ShowReactJsonField = (props) => {
    const { validate, jsonValue, collapsed, label, autocomplete } = props

    const [parsedCollapse, setParsedCollapse] = React.useState(collapsed)
    const [open, setOpen] = React.useState(false);
    const [anchorPosition, setAnchorPosition] = React.useState({
      top: 750,
      left: 16,
    });

    const isFirstRender = React.useRef(true)
    useEffect(() => {
      console.log("IN useeffectt " + autocomplete)

      if (isFirstRender.current) {
        isFirstRender.current = false;
        console.log("IN useeffectt (2)" + collapsed)
        return;
      }
    }, [])
    /*
    componentWillUpdate = (nextProps, nextState) => {
      console.log(nextProps, nextState)
        //nextState.value = nextProps.a + nextProps.b;
    }
    */

    const jsonRef = React.useRef()

    return (
      <span>
        <ReactJson
          ref={jsonRef}
          src={validate.result}
          theme={theme.palette.jsonTheme}
          style={theme.palette.reactJsonStyle}
          shouldCollapse={(jsonField) => {
            return collapseField(jsonField)
          }}
          iconStyle={theme.palette.jsonIconStyle}
          collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
          displayArrayKey={false}
          enableClipboard={(copy) => {
            handleReactJsonClipboard(copy);
          }}
          displayDataTypes={false}
          onClick={(event) => {
            const pos = {
              top: event.screenX,
              left: event.screenY,
            }

            setAnchorPosition(pos)
          }}
          onSelect={(select) => {
            setOpen(true)

            setTimeout(() => {
              setOpen(false)
            }, 2500)

            //setAnchorPosition({
            //	top: 300,
            //	right: 300,
            //})
            //setAnchorEl(jsonRef.current)
            HandleJsonCopy(jsonValue, select, autocomplete);
            console.log("SELECTED!: ", select);
          }}
          name={label}
        />
        {anchorPosition !== null ?
          <Popover
            id="mouse-over-popover-right"
            sx={{
              pointerEvents: 'none',
            }}
            open={open}
            anchorReference="anchorPosition"
            anchorPosition={anchorPosition}
            style={{ zIndex: 50000, }}
            onClose={(event) => {
              setAnchorPosition({
                top: 750,
                left: 16,
              })
            }}
            disableRestoreFocus
          >
            <Typography style={{ padding: 5 }}>
              Copying
            </Typography>
          </Popover>
          : null}
      </span>
    )
  }

  const changeExecution = (data) => {
    if ((data.result === undefined || data.result === null || data.result.length === 0) && data.status !== "FINISHED" && data.status !== "ABORTED") {
      start()
      setExecutionRunning(true)
      setExecutionRequestStarted(false)
    }

    var checkStarted = false
    if (data.results !== undefined && data.results !== null && data.results.length > 0) {
      if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) {
        setExecutionData({});
        checkStarted = true
        start();
        setExecutionRunning(true);
        setExecutionRequestStarted(false);
      } else {
        if (data.results !== undefined && data.results !== null) {
          for (let resultkey in data.results) {
            if (data.results[resultkey].status !== "SUCCESS") {
              continue
            }

            if (data.results[resultkey].result.includes("too large")) {
              setExecutionData({});
              checkStarted = true
              start();
              setExecutionRunning(true);
              setExecutionRequestStarted(false);
              break
            }
          }
        }
      }
    }

    const cur_execution = {
      execution_id: data.execution_id,
      authorization: data.authorization,
    }

    setExecutionRequest(cur_execution)
    setExecutionModalView(1)

    if (!checkStarted) {
      handleUpdateResults(data, cur_execution)

      if (cy !== undefined && cy !== null) {
        cy.elements().removeClass("success-highlight failure-highlight executing-highlight");
        for (let actionKey in data.workflow.actions) {
          var actionitem = data.workflow.actions[actionKey]

          handleColoring(actionitem.id, "", actionitem.label)
        }

        for (let resultKey in data.results) {
          var item = data.results[resultKey]

          handleColoring(item.action.id, item.status, item.action.label)
        }
      }

      setExecutionData(data)
    }
  }

  // Should probably put this on the backend instead when notifications are made :))
  const getErrorSuggestion = (result) => {
    if (result === undefined || result === null) {
      return ""
    }

    // Check if array with json inside to handle one item at a time~
    if (typeof result === "object" && result.length !== undefined) {
      if (result.length > 0) {
        // Check type inside
        if (typeof result[0] === "object") {
          result = result[0]
        }
      }
    }

    if (result.success === true && result.status === 200) {
      if (result.body !== undefined && result.body !== null) {
        const stringbody = result.body.toString()
        if ((stringbody.startsWith("{") && stringbody.endsWith("}")) || (stringbody.startsWith("[") && stringbody.endsWith("]"))) {
          return ""
        }

        if (stringbody.length > 1000) {
          return "Body looks to be big in a standard format. Consider using the 'To File' parameter to automatically make it into a file."
        }
      }
    }

    if (result.status === 429) {
      return "Rate limit exceeded. Consider using a different API key or wait a bit before trying again."
    }

    if (result.status === 405) {
      return `Method not allowed. Check the URL to ensure it has all the required parameters. If you keep getting a 405, please forward a screenshot of this to ${supportEmail}`
    }

    if (result.status === 415) {
      return "Content-Type header missing or wrong. Please add the correct Content-Type header and save the workflow."
    }

    if (result.status === 401) {
      return "Authentication failed (401). The URL or auth key is wrong. Check the body of the result for more information."
    }

    if (result.status === 403) {
      return "Authorization failed (403). The API user most likely doesn't have the correct permissions. Check the body of the result for more information."
    }

    if (result.status === 404) {
      return "The URL, or content of the URL is incorrect. Check it and try again."
    }

    if (result.status === 400) {
      return "The queries or data sent to the API is most likely wrong (400). Check the body of the result for more information."
    }

    if (result.status === 200 || result.status === 201 || result.status === 204) {
      return "It looks like the result was successful! If it didn't work, make sure to check if the body you are sending was correct."
    }


    // Validate and check for newlines
    if (result.success !== false) {

      var stringjson = result
      const valid = validateJson(stringjson, true)
      if (valid.valid === false) {
        if (stringjson.startsWith("{") && stringjson.endsWith("}")) {
          // Look for newline
          if (stringjson.includes("\n") && !stringjson.includes("\n")) {
            return "Looks like you have a newline problem. Consider using the | replace: '\n', '\\n' }} filter in Liquid."
          } else {
            return "The result looks like it should be JSON, but is invalid. Look for potential single quotes instead of double quotes, missing commas or newlines"
          }
        }
      }

      //return ""
    }


    try {
      stringjson = JSON.stringify(result)
    } catch (e) {
    }

    stringjson = stringjson.toLowerCase()
    if (stringjson.includes("localhost")) {
      return "You can't use localhost in apps. Use the external ip or url of the server instead"
    }

    if (stringjson.includes("manifest unknown")) {
      return "The app's Docker Image is not available in the environment yet. Re-run the app to force a re-download of the app. If the problem persists, contact support"
    }

    if (stringjson.toLowerCase().includes("too many values to unpack")) {
		return "This is a known error with old apps. Please rebuild the app. Contact support@shuffler.io if it persists after rebuild."
	}

    if (result.status !== 200 && result.url !== undefined && result.url !== null && typeof result.url === "string" && (result.url.includes("192.168") || result.url.includes("172.16") || result.url.includes("10.0"))) {
      return "Consider whether your Orborus environment can connect to a local IP or not."
    }

    if (stringjson.includes("kms/")) {
      return `KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=notifications. If you need help with KMS, please contact ${supportEmail}`
    }

    if (stringjson.includes("string indices must be integers")) {
      return `String indices must be integers typically means you are getting a list, while you expected a dictionary. Check the Variable & Debug for more information.` 
    }

    if (stringjson.includes("invalidurl")) {
      // IF count of "http" is more than one, 1, it's prolly invalid
      var additionalinfo = ""
      if (stringjson.includes("http") && stringjson.match(/http/g).length > 1) {
        additionalinfo = "You may be using multiple 'http' in the URL. "
      }

      return "The URL is invalid. Change the URL to a valid one, and try again. " + additionalinfo
    }

    if (stringjson.includes("result too large to handle")) {
      return "Execution loading failed. Reload the execution by closing it and clicking it again"
    }

    if (isCloud && stringjson.toLowerCase().includes("timeout error")) {
      return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=locations to create an environment to connect to"
    }

    if (stringjson.toLowerCase().includes("invalid header")) {
      return "A header or authentication token in the app is invalid. Check the app's configuration"
    }


    if (stringjson.includes("connectionerror")) {
      if (stringjson.includes("kms")) {
        return `KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=notifications&kms=true. If you need help with KMS, please contact ${supportEmail}`
      }

      return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs."
    }


    return ""
  }

  const ShowCopyingTooltip = () => {
    const [showCopying, setShowCopying] = React.useState(true)

    if (!showCopying) {
      return false
    }

    return (
      <Tooltip title={"Copying"} placement="left-start">
        <div />
      </Tooltip>
    )
  }

  var nonskippedResults = []
  if (executionData.results !== undefined) {
    const newSkipped = executionData.results.find((result) => result.status !== "SKIPPED")
    if (newSkipped !== undefined) {
      nonskippedResults = newSkipped
    }
  }

  const envStatus = !(executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0) ? "loading" : "success"

  var executionDelay = -75

  const executionModal = (
    <Drawer
      anchor={"right"}
      open={executionModalOpen}
      onClose={() => {
        setExecutionModalOpen(false)
        setBuildDebugMode("build")
        //const newitem = removeParam("execution_id", cursearch);
        //navigate(curpath + newitem)
      }}
      style={{
        resize: "both",
        overflow: "auto",
      }}
      hideBackdrop={false}
      variant="temporary"
      BackdropProps={{
        style: {
          //backgroundColor: "transparent",
        }
      }}
      PaperProps={{
        style: {
          resize: "both",
          overflow: "auto",
          minWidth: isMobile ? "100%" : 490,
          maxWidth: isMobile ? "100%" : 490,
          color: theme.palette.text.primary,
          fontSize: 18,
          borderLeft: theme.palette.defaultBorder,

          borderRadius: theme.palette.borderRadius,
          borderTopRightRadius: 0,
          borderBottomRightRadius: 0,
          backgroundColor: themeMode === "dark" ? "black" : theme.palette.drawer.backgroundColor,
        },
      }}
    >
      {isMobile ?
        <Tooltip
          title="Close window"
          placement="top"
          style={{ zIndex: 10011 }}
        >
          <IconButton
            style={{ zIndex: 5000, position: "absolute", top: 10, right: 10 }}
            onClick={(e) => {
              e.preventDefault();
              setExecutionModalOpen(false)
            }}
          >
            <CloseIcon style={{ color: theme.palette.text.primary }} />
          </IconButton>
        </Tooltip>
        : null}

      {executionModalView === 0 ? (
        <div style={{ position: "relative", padding: isMobile ? "0px 0px 0px 10px" : "25px 25px 25px 25px", zIndex: 12502,  backgroundColor: theme.palette.drawer.backgroundColor,  height: "100%", }}>
          <div style={{ display: "flex", }}>
            <Breadcrumbs
              aria-label="breadcrumb"
              separator="›"
              style={{ color: theme.palette.text.primary, fontSize: 16, textWrap: "nowrap" }}
            >
              <h2 style={{ color: theme.palette.text.primary}}>
                <DirectionsRunIcon style={{ marginRight: 0, }} />
                All Workflow Runs
              </h2>
            </Breadcrumbs>
            <Tooltip
              title={"Explore and Debug all Workflow Runs"}
              placement="left-start"
              style={{ zIndex: 10010 }}
            >
              <a target="_blank" href={`/workflows/debug?workflow_id=${workflow.id}`} style={{ textDecoration: "none", }}>
                <Button
                  color="secondary"
                  style={{ marginLeft: 125, maxHeight: 30, marginTop: 20, }}
                >
                  <QueryStatsIcon style={{ color: theme.palette.text.primary, }} />
                </Button>
              </a>
            </Tooltip>
          </div>

		  {executionTimeline?.length > 0 && 
		  	<div style={{borderTop: "1px solid rgba(255,255,255,0.3)", position: "fixed", bottom: 0, zIndex: 13000, width: 460, right: 10, padding: "0px 10px 0px 10px", backgroundColor: theme.palette.drawer.backgroundColor, }}>
			  <LineChartWrapper 
				inputname={""}
				keys={executionTimeline}
				height={100}
				width={100}
				border={false}

			    color={"#808080"}
			  />
			</div>
		  }

		  <Button
		    style={{ borderRadius: 8, }}
		    variant="outlined"
		    fullWidth
		    onClick={() => {
		  	getWorkflowExecution(workflow.id, "", executionFilter, workflow.org_id)
		    }}
		    color="secondary"
		  >
		    <CachedIcon style={{ marginRight: 10 }} />
		    Refresh Runs
		  </Button>
          <ButtonGroup
            fullWidth
            style={{ marginTop: 5, maxHeight: 50, overflow: "hidden", }}>
		
            <Button
              color="secondary"
              variant={executionFilter === "ALL" ? "contained" : "outlined"}
              onClick={() => {
                setExecutionFilter("ALL")
          	    getWorkflowExecution(workflow.id, "", "ALL", workflow.org_id)
              }}
            >
              All
            </Button>
            <Button
              color="secondary"
              variant={executionFilter === "FINISHED" ? "contained" : "outlined"}
              onClick={() => {
                setExecutionFilter("FINISHED")
          	    getWorkflowExecution(workflow.id, "", "FINISHED", workflow.org_id)
              }}
            >
              Finished
            </Button>
            <Button
              color="secondary"
              variant={executionFilter === "EXECUTING" ? "contained" : "outlined"}
              onClick={() => {
                setExecutionFilter("EXECUTING")
          	    getWorkflowExecution(workflow.id, "", "EXECUTING", workflow.org_id)
              }}
            >
              Executing
            </Button>
            <Button
              color="secondary"
              variant={executionFilter === "ABORTED" ? "contained" : "outlined"}
              onClick={() => {
                setExecutionFilter("ABORTED")
          	    getWorkflowExecution(workflow.id, "", "ABORTED", workflow.org_id)
              }}
            >
              Aborted
            </Button>
          </ButtonGroup>

          <div
            style={{
              marginTop: 10,
              marginBottom: 10,
            }}
          />

          {workflowExecutions.length > 0 ? (
            <div>
              {workflowExecutions.map((data, index) => {
                executionDelay += 50

                const statusColor =
                  data.status === "FINISHED"
                    ? green : data.status === "ABORTED" || data.status === "FAILED"
                      ? "red"
                      : yellow;

                const successActions =
                  data.results !== undefined && data.results !== null
                    ? data.results.filter((result) => result.status === "SUCCESS").length
                    : 0

                const skippedActions = data.results !== undefined && data.results !== null ?
                  data.results.filter((result) => result.status === "SKIPPED").length
                  : 0

                const timestamp = new Date(data.started_at * 1000)
                  .toLocaleString("en-GB")
                  .split(".")[0]
                  .split("T")
                  .join(" ");

                var calculatedResult =
                  data.workflow.actions !== undefined &&
                    data.workflow.actions !== null
                    ? data.workflow.actions.length
                    : 0;

                if (data.workflow.triggers !== undefined && data.workflow.triggers !== null) {
                  for (let triggerkey in data.workflow.triggers) {
                    const trigger = data.workflow.triggers[triggerkey];
                    if (
                      (trigger.app_name === "User Input" &&
                        trigger.trigger_type === "USERINPUT") ||
                      (trigger.app_name === "Shuffle Workflow" &&
                        trigger.trigger_type === "SUBFLOW")
                    ) {
                      calculatedResult += 1;
                    }
                  }
                }

                const foundnotifications = data.notifications_created === undefined || data.notifications_created === null ? 0 : data.notifications_created

			    const executionPaperStyle = {
			      padding: 5,
			      marginTop: 5,
			      minHeight: 45,
			      maxHeight: 45,
			      minWidth: "95%",
			      maxWidth: "95%",
			      display: "flex",
			      marginBottom: index === workflowExecutions.length-1 ? 200 : 10,
			      cursor: "pointer",
			      color: theme.palette.text.primary,
			      borderRadius: theme.palette.borderRadius,
			      backgroundColor: theme.palette.platformColor,
			    };

                return (
                  <span
					key={index}
				  >
                    {/*<Zoom key={index} in={true} style={{ transitionDelay: `${executionDelay}ms` }}>*/}
                    <div>
                      <Tooltip
                        key={data.execution_id}
                        title={data.result}
                        placement="left-start"
                        style={{ zIndex: 10010 }}
                      >
                        <Paper
                          elevation={5}
                          key={data.execution_id}
                          square
                          style={executionPaperStyle}
                          onMouseOver={() => { }}
                          onMouseOut={() => { }}
                          onClick={() => {
                            if ((data.result === undefined || data.result === null || data.result.length === 0) && data.status !== "FINISHED" && data.status !== "ABORTED") {
                              start()
                              setExecutionRunning(true)
                              setExecutionRequestStarted(false)
                            }

                            navigate(`?execution_id=${data.execution_id}`)

                            // Ensuring we have the latest version of the result.
                            // Especially important IF the result is > 1 Mb in cloud
                            var checkStarted = false
                            if (data.results !== undefined && data.results !== null && data.results.length > 0) {
                              if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) {
                                setExecutionData({});
                                checkStarted = true
                                start();
                                setExecutionRunning(true);
                                setExecutionRequestStarted(false);
                              } else {
                                if (data.results !== undefined && data.results !== null) {
                                  for (let resultkey in data.results) {
                                    if (data.results[resultkey].status !== "SUCCESS") {
                                      continue
                                    }

                                    if (data.results[resultkey].result.includes("too large")) {
                                      setExecutionData({});
                                      checkStarted = true
                                      start();
                                      setExecutionRunning(true);
                                      setExecutionRequestStarted(false);
                                      break
                                    }
                                  }
                                }
                              }
                            }

                            const cur_execution = {
                              execution_id: data.execution_id,
                              authorization: data.authorization,
                            }

                            setExecutionRequest(cur_execution)
                            setExecutionModalView(1)

                            if (!checkStarted) {
                              handleUpdateResults(data, cur_execution)

                              if (cy !== undefined && cy !== null) {
                                cy.elements().removeClass("success-highlight failure-highlight executing-highlight");
                                for (let actionKey in data.workflow.actions) {
                                  var actionitem = data.workflow.actions[actionKey]

                                  handleColoring(actionitem.id, "", actionitem.label)
                                }

                                for (let resultKey in data.results) {
                                  var item = data.results[resultKey]

                                  handleColoring(item.action.id, item.status, item.action.label)
                                }
                              }

                              setExecutionData(data)
                            }
                          }}
                        >
                          <div style={{ display: "flex", flex: 1 }}>
                            <div
                              style={{
                                marginLeft: 0,
                                width: lastExecution === data.execution_id ? 4 : 2,
                                backgroundColor: statusColor,
                                marginRight: 5,
                                maxHeight: 40,
                              }}
                            />
                            <Tooltip
                              color="primary"
                              title={`Execution Source: ${data.execution_source === 'default' ? 'manual run' : data.execution_source}.` + (data.authgroup !== undefined && data.authgroup !== null && data.authgroup.length > 0 ? ` Authgroup: ${data.authgroup}` : '')}
                              placement="left"
                            >
                              <div
                                style={{
                                  height: "100%",
                                  width: 40,
                                  borderColor: theme.palette.text.primary,
                                  marginRight: 15,
                                }}
                              >
                                {getExecutionSourceImage(data)}
                              </div>
                            </Tooltip>
                            <div
                              style={{
                                marginTop: "auto",
                                marginBottom: "auto",
                                marginRight: 15,
                                fontSize: 13,
                                color: theme.palette.text.primary,
                              }}
                            >
                              {timestamp}
                            </div>
                            {data.workflow.actions !== null ? (
                              <Tooltip
                                color="primary"
                                title={`${successActions} action(s) ran + ${skippedActions} skipped = ${calculatedResult} total node(s). If this doesn't add up, there is most likely a problem with the workflow (e.g. ABORTED/FAILURE)`}
                                placement="top"
                              >
                                <div
                                  style={{
                                    marginRight: 10,
                                    marginLeft: 10,
                                    marginTop: "auto",
                                    marginBottom: "auto",
									                  color: theme.palette.text.primary,
                                  }}
                                >
                                  {successActions} <span style={{ color: theme.palette.text.primary }}>+</span> {skippedActions > 0 ? skippedActions : <span style={{ color: theme.palette.text.primary}}>{skippedActions}</span>} <span style={{ color: theme.palette.text.primary }}>=</span> {calculatedResult}
                                </div>
                              </Tooltip>
                            ) : null}
                          </div>

                          {foundnotifications > 0 ?
                            <Tooltip title={"This workflow created " + foundnotifications + " notification(s)"} placement="top">
                              <ErrorOutlineIcon
                                style={{ color: "#f85a3e", marginTop: 10, marginRight: 10, }}
                                onClick={(e) => {
                                  e.preventDefault()
                                  e.stopPropagation()
                                  window.open(`/admin?org_id=${workflow.org_id}&admin_tab=notifications&workflow=${data.workflow.id}&execution_id=${data.execution_id}`, "_blank")
                                }}
                              />
                            </Tooltip>
                            : null}

                          <Tooltip title={"Inspect this run"} placement="top">
                            {lastExecution === data.execution_id ? (
                              <KeyboardArrowRightIcon
                                style={{
                                  color: "#FF8544",
                                  marginTop: "auto",
                                  marginBottom: "auto",
                                }}
                              />
                            ) : (
                              <KeyboardArrowRightIcon
                                style={{ marginTop: "auto", marginBottom: "auto" }}
                              />
                            )}
                          </Tooltip>
                        </Paper>
                      </Tooltip>
                    </div>
                  </span>
                );
              })}
            </div>
          ) : (
            <Fade in={true} timeout={1000} style={{ transitionDelay: `${150}ms` }}>
              <div style={{ marginTop: 100, marginBottom: 0 }}>
                <Typography variant="body1" color="textSecondary" style={{textAlign: "center", }}>
                  No executions found for the '{executionFilter}' filter.
                </Typography>

                <Button
                  fullWidth
                  variant="outlined"
                  sx={{
                    marginTop: 1,
                    textTransform: "none",
                    border: `1px solid ${green}`,
                    color: green,
                    "&:hover": {
                      backgroundColor: `${green}20`,
                      color: green,
                      border: `1px solid ${green}`,
                    },
                  }}
                  onClick={() => {
                    executeWorkflow(executionText, workflow.start, lastSaved);
                  }}
                >
                  Test Workflow <PlayArrowIcon style={{ marginLeft: 15, }} />
                </Button>
              </div>
            </Fade>
          )}
        </div>
      ) : (
        <div style={{ backgroundColor: theme.palette.drawer.backgroundColor, padding: isMobile ? "0px 10px 50px 10px" : "25px 15px 150px 15px", maxWidth: isMobile ? "100%" : "100%", overflowX: "hidden", height: "100%"}}>


          <Breadcrumbs
            aria-label="breadcrumb"
            separator="›"
            style={{ color: theme.palette.text.primary, fontSize: 16 }}
          >
            <span
              style={{ color: "rgba(255,255,255,0.5)", display: "flex" }}
              onClick={() => {
                setExecutionRunning(false);
                stop();
                // getWorkflowExecution(currentWorkflow.id, "");
                getWorkflowExecution(props.match.params.key, "");
                setExecutionModalView(0);
                setLastExecution(executionData.execution_id);
              }}
            >
              <IconButton
                style={{
                  paddingLeft: 0,
                  marginTop: "auto",
                  marginBottom: "auto",
                }}
                onClick={() => {
                  setExecutionRunning(false);
                  stop()
                }}
              >
                <ArrowBackIcon style={{ color: theme.palette.text.primary }} />
              </IconButton>
                <h2
                  style={{ color: theme.palette.text.primary, cursor: "pointer" }}
                  onClick={() => {
                    const newitem = removeParam("execution_id", cursearch);
                    navigate(curpath + newitem)
                    setExecutionRunning(false);
                    stop()
                  }}
                >
                  Back to all runs
                </h2>
            </span>
          </Breadcrumbs>
          <Divider
            style={{
              backgroundColor: theme.palette.defaultBorder,
              marginTop: 10,
              marginBottom: 10,
            }}
          />
          <div style={{ display: "flex", paddingLeft: 10, paddingRight: 10, position: "sticky", top: 0, zIndex: 12500, backgroundColor: theme.palette.drawer.backgroundColor, borderRadius: theme.palette.borderRadius, border: "2px solid rgba(255,255,255,0.3)", marginBottom: 10, }}>
            <h2>Details</h2>
            <Tooltip
              color="primary"
              title={
				  <Typography variant="body1">
					Rerun workflow. Uses same startnode as the original. Runs from scratch.
				  </Typography>
			  }
              placement="left"
              style={{ zIndex: 50000 }}
            >
              <span style={{}}>
                <Button
                  color="primary"
                  style={{ float: "right", marginTop: 20, marginLeft: 10, }}
                  onClick={() => {
                    const skip_popup = true
                    executeWorkflow(
                      executionData.execution_argument,
                      executionData.start,
                      lastSaved,
                      skip_popup,
                    )

                    if (executionText === undefined || executionText === null || executionText.length === 0) {
                      setExecutionText(executionData.execution_argument)
                    }

                    setExecutionModalOpen(false);
                  }}
                >
                  <CachedIcon style={{}} />
                </Button>
              </span>
            </Tooltip>

            <Tooltip
              color="primary"
              title="Previous execution"
              placement="top"
              style={{ zIndex: 50000, }}
            >
              <span style={{}}>
                <Button
                color="primary"
                  style={{ float: "right", marginTop: 20, }}
                  onClick={() => {
                    // Find current one in execution list
                    var nextindex = -1
                    const currentIndex = workflowExecutions.findIndex((item) => item.execution_id === executionData.execution_id)
                    if (currentIndex === -1) {
                      nextindex = workflowExecutions.length - 1
                    } else {
                      nextindex = currentIndex - 1
                    }

                    if (nextindex < 0) {
                      toast.warn("Use the workflow run debugger to dig deeper - nothing more to show here.")
                      return
                    }

                    const data = workflowExecutions[nextindex]
                    navigate(`?execution_id=${data.execution_id}`)

                    changeExecution(data)
                  }}
                >
                  <ArrowBackIcon />
                </Button>
              </span>
            </Tooltip>

            <Tooltip
              color="primary"
              title="Next execution"
              placement="top"
              style={{ zIndex: 50000, }}
            >
              <span style={{}}>
                <Button
                  color="primary"
                  style={{ float: "right", marginTop: 20}}
                  onClick={() => {
                    // Find current one in execution list
                    var nextindex = -1
                    const currentIndex = workflowExecutions.findIndex((item) => item.execution_id === executionData.execution_id)
                    if (currentIndex === -1) {
                      nextindex = 0
                    } else {
                      nextindex = currentIndex + 1
                    }

                    if (nextindex >= workflowExecutions.length) {
                      toast.warn("Use the workflow run debugger to dig deeper - nothing more to show here.")
                      return
                    }

                    const data = workflowExecutions[nextindex]
                    navigate(`?execution_id=${data.execution_id}`)

                    changeExecution(data)
                  }}
                >
                  <ArrowForwardIcon />
                </Button>
              </span>
            </Tooltip>

            {executionData.status === "EXECUTING" ? (
              <Tooltip
                color="primary"
                title="Abort workflow"
                placement="top"
                style={{ zIndex: 50000 }}
              >
                <span style={{}}>
                  <Button
                    color="primary"
                    style={{ float: "right", marginTop: 20, }}
                    onClick={() => {
                      abortExecution();
                    }}
                  >
                    <PauseIcon style={{}} />
                  </Button>
                </span>
              </Tooltip>
            ) :
              <Tooltip
                color="primary"
                title={`Check Notifications (${executionData.notifications_created === undefined || executionData.notifications_created === null || executionData.notifications_created === 0 ? 0 : executionData.notifications_created})`}
                placement="top"
                style={{ zIndex: 50000 }}
              >
                <span style={{}}>
                  <Button
                    color={executionData.notifications_created === undefined || executionData.notifications_created === null || executionData.notifications_created === 0 ? "secondary" : "primary"}
                    style={{ float: "right", marginTop: 20, }}
                    disabled={executionData.notifications_created === undefined || executionData.notifications_created === null || executionData.notifications_created === 0}
                  >
                    <ErrorOutlineIcon
                      style={{}}
                      onClick={(e) => {
                        e.preventDefault()
                        e.stopPropagation()
                        window.open(`/admin?admin_tab=notifications&workflow=${executionData.workflow.id}&execution_id=${executionData.execution_id}`, "_blank")
                      }}
                    />
                  </Button>
                </span>
              </Tooltip>
            }

            {isCloud ?
              <Tooltip
                color="primary"
                title="Explore logs for the workflow (up to 5 days back)"
                placement="top"
                style={{ zIndex: 50000, }}
              >
                <span style={{}}>
                  <Button
                    color="secondary"
                    style={{ float: "right", marginTop: 20, }}

                    // Max 5 days in the past
                    disabled={executionData.started_at < (Math.floor(Date.now() / 1000) - 432000)}
                    onClick={() => {
                      toast("Opening logs in a new tab")

                      setTimeout(() => {
                        window.open(`${globalUrl}/api/v1/workflows/search/${executionData.execution_id}`, "_blank")
                      }, 250)
                    }}
                  >
                    <InsightsIcon />
                  </Button>
                </span>
              </Tooltip>
              : null}

          </div>

          {executionData.status !== undefined &&
            executionData.status.length > 0 ? (
            <div style={{ display: "flex", marginLeft: 10, }}>
              <Typography variant="body1">
                <b>Status &nbsp;&nbsp;</b>
              </Typography>
              <Typography variant="body1" color="textSecondary">
                {executionData.status}
              </Typography>
            </div>
          ) : null}

          {executionData.execution_source !== undefined &&
            executionData.execution_source !== null &&
            executionData.execution_source.length > 0 &&
            executionData.execution_source !== "default" ||
            (executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0) ? (
            <div style={{ display: "flex", marginLeft: 10, }}>
              <Typography variant="body1">
                <b>Source &nbsp;&nbsp;</b>
              </Typography>
              <Typography variant="body1" color="textSecondary">

                {executionData?.execution_source?.startsWith("datastore") ?
					  <a
						rel="noopener noreferrer"
						href={`/admin?tab=datastore${executionData.execution_source.split("|").length > 2 ? "&category="+executionData.execution_source.split("|")[1]+"&key="+executionData.execution_source.split("|")[2] : ""}`}
						target="_blank"
						style={{ textDecoration: "none", color: theme.palette.linkColor }}
					  >
						Datastore Automation
					  </a>
					:
                executionData.execution_source === "authgroups" || (executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0) ?
                  <a
                    rel="noopener noreferrer"
                    href={`/admin?tab=app_auth`}
                    target="_blank"
                    style={{ textDecoration: "none", color: theme.palette.linkColor }}
                  >
                    Auth Group '{executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0 ? `${executionData.authgroup}` : null}'
                  </a>
                  :
                  executionData.execution_parent !== null &&
                    executionData.execution_parent !== undefined &&
                    executionData.execution_parent.length > 0 ? (
                    executionData.execution_source === props.match.params.key ?
                      <span
                        style={{ cursor: "pointer", color: "#FF8544" }}
                        onClick={(event) => {
                          getWorkflowExecution(
                            props.match.params.key,
                            executionData.execution_parent
                          );
                        }}
                      >
                        Parent Execution
                      </span>
                      :
                      <a
                        rel="noopener noreferrer"
                        href={`/workflows/${executionData.execution_source}?view=executions&execution_id=${executionData.execution_parent}`}
                        target="_blank"
                        style={{ textDecoration: "none", color: theme.palette.linkColor }}
                      >
                        Parent Workflow
                      </a>
                  )
                    :
                    executionData.execution_source === "questions" || executionData.execution_source === "web" || executionData.execution_source === "form" || executionData.execution_source === "forms" ?
                      <a
                        rel="noopener noreferrer"
                        href={`/forms/${executionData.workflow.id}`}
                        target="_blank"
                        style={{ textDecoration: "none", color: theme.palette.linkColor }}
                      >
                        Form
                      </a>
                      :
                      executionData.execution_source
                }
              </Typography>
            </div>
          ) : null}
          {executionData.started_at !== undefined ? (
            <div style={{ display: "flex", marginLeft: 10, }}>
              <Typography variant="body1">
                <b>Started &nbsp;&nbsp;</b>
              </Typography>
              <Typography variant="body1" color="textSecondary">
                {new Date(executionData.started_at * 1000).toLocaleString("en-GB")}
              </Typography>
            </div>
          ) : null}
          {executionData.completed_at !== undefined &&
            executionData.completed_at !== null &&
            executionData.completed_at > 0 ? (
            <div style={{ display: "flex", marginLeft: 10, }}>
              <Typography variant="body1" onClick={() => {
                console.log(executionData)
              }}>
                <b>Finished &nbsp;</b>
              </Typography>
              <Typography variant="body1" color="textSecondary">
                {new Date(executionData.completed_at * 1000).toLocaleString("en-GB")}
              </Typography>
            </div>

          ) : null}

          {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?

            <div style={{ display: "flex", marginLeft: 10, }}>
              <Typography variant="body1">

                {/*envStatus === "success" ?
					<Tooltip title="Environment is healthy" placement="top">
						<CheckCircleIcon style={{ color: "green" }} />
					</Tooltip>
					: envStatus === "failure" ?
					<Tooltip title="Environment is unhealthy" placement="top">
						<ErrorIcon style={{ color: "red" }} />
					</Tooltip>
				: null*/}

                <b style={{}}>Location &nbsp;</b>
              </Typography>

              <Typography variant="body1" color="textSecondary" style={{ color: "#FF8544", cursor: "pointer", }} onClick={() => {
                window.open("/admin?tab=locations", "_blank")
              }}>
                {executionData.workflow.actions[0].environment}
              </Typography>

            </div>
            : null}

          {userdata.support === true && executionData.workflow !== undefined && executionData.workflow !== null && executionData.status !== "EXECUTING" && executionData.status !== "ABORTED" ?
            <div style={{ marginTop: 5, marginBottom: 5, }}>
              <WorkflowValidationTimeline
                originalWorkflow={workflow}

                apps={apps}
                workflow={executionData.workflow}
                getParents={getParents}

                execution={executionData}
              />
            </div>
            : null}

          <div style={{ marginTop: 10 }} />

          {executionData.execution_argument !== undefined && executionData.execution_argument !== null &&
            executionData.execution_argument.length > 1
            ? parsedExecutionArgument()
            :
            null}

          <Divider
            style={{
              backgroundColor: theme.palette.defaultBorder,
              marginTop: 15,
              marginBottom: 20,
            }}
          />

          {executionData.results !== undefined &&
            executionData.results !== null &&
            executionData.results.length > 1 &&
            executionData.results.find(
              (result) =>
                result.status === "SKIPPED"
            ) ? (
            <FormControlLabel
              style={{ color: theme.palette.text.primary, marginBottom: 10 }}
              label={
                <div style={{ color: theme.palette.text.primary }}>
                  Show skipped actions
                </div>
              }
              control={
                <Switch
                  checked={showSkippedActions}
                  onChange={() => {
                    setShowSkippedActions(!showSkippedActions);
                  }}
                />
              }
            />
          ) : null}

          <div style={{ display: "flex", marginTop: 10, marginBottom: 30 }}>
            <div>
              {executionData.status !== undefined &&
                executionData.status !== "ABORTED" &&
                executionData.status !== "FINISHED" &&
                executionData.status !== "FAILURE" &&
                executionData.status !== "WAITING" &&
                !(executionData.results === undefined || executionData.results === null || (executionData.results.length === 0 && executionData.status === "EXECUTING")) ? (
                <div style={{}}>
                  <CircularProgress style={{ marginLeft: 145, marginBottom: 10, }} onClick={() => {
                    console.log(environments, defaultEnvironmentIndex, nonskippedResults)
                  }} />

                  {environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
                    <Typography variant="body2" color="textSecondary" style={{}}>
                      No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? <a href="/admin?tab=locations" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Find out here</a>. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: <a href={`mailto:${supportEmail}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>{supportEmail}</a>
                      No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? <a href="/admin?tab=locations" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor }}>Find out here</a>. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: <a href="mailto:support@shuffler.io" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor}}>support@shuffler.io</a>
                    </Typography>
                    : null}
                </div>
              ) : null}
            </div>
          </div>

          {
            executionData.results === undefined ||
              executionData.results === null ||
              (executionData.results.length === 0 && executionData.status === "EXECUTING") ? (

              <div style={{}}>
                <CircularProgress style={{ marginLeft: 145, marginBottom: 10, }} />
                {environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
                  <Typography variant="body2" color="textSecondary" style={{}}>
                    No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? <a href="/admin?tab=locations" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor }}>Learn more</a>. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: <a href={`mailto:${supportEmail}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: theme.palette.linkColor }}>{supportEmail}</a>
                  </Typography>
                  :
                  null}
              </div>
            ) : (
              executionData.results.map((data, index) => {
                if (executionData.results.length !== 1 && !showSkippedActions && (data.status === "SKIPPED")) {
                  return null;
                }

          		const showRerun = new URLSearchParams(cursearch).get("rerun")
				if (showRerun === "true") {
          			const showNode = new URLSearchParams(cursearch).get("node")
					if (data.action.id !== showNode) {
						return null
					}
				}

                // FIXME: The latter replace doens't really work if ' is used in a string
                var showResult = data.result.trim();
                const validate = validateJson(showResult);

                const curapp = apps.find(
                  (a) =>
                    a.name === data.action.app_name &&
                    a.app_version === data.action.app_version
                );
                const imgsize = 50;
                const statusColor =
                  data.status === "FINISHED" || data.status === "SUCCESS"
                    ? green
                    : data.status === "ABORTED" || data.status === "FAILURE"
                      ? "red"
                      : yellow;

                var imgSrc = curapp === undefined ? "" : curapp.large_image;
                if (imgSrc.length === 0 && workflow.actions !== undefined && workflow.actions !== null) {
                  // Look for the node in the workflow
                  const action = workflow.actions.find(
                    (action) => action.id === data.action.id
                  )
                  if (action !== undefined && action !== null) {
                    imgSrc = action.large_image;
                  }
                }

                if ((imgSrc === undefined || imgSrc === null || imgSrc.length === 0) && cy !== undefined && cy !== null) {
                  const foundnode = cy.getElementById(data.action.id)
                  if (foundnode !== undefined && foundnode !== null && foundnode.length > 0) {
                    // FIXME: Find image from cytoscape action
                  } else {
                    for (let actionkey in workflow.actions) {
                      if (workflow.actions[actionkey].app_name === data.action.app_name || workflow.actions[actionkey].id === data.action.id || workflow.actions[actionkey].label === data.action.label || workflow.actions[actionkey].name === data.action.name) {

                        if (workflow.actions[actionkey].large_image !== undefined && workflow.actions[actionkey].large_image !== null && workflow.actions[actionkey].large_image.length > 0) {
                          imgSrc = workflow.actions[actionkey].large_image
                          break
                        }
                      }
                    }
                  }
                }


                var actionimg =
                  curapp === null ? null : (
                    <img
                      alt={data.action.app_name}
                      src={imgSrc}
                      style={{
                        marginRight: 20,
                        width: imgsize,
                        height: imgsize,
                        border: `2px solid ${statusColor}`,
                        borderRadius: executionData.start === data.action.id ? 25 : 5,

						cursor: isCloud ? "pointer" : "default",
                      }}
					  onClick={() => {
						  if (isCloud) { 
							window.open(`/apps/${data?.action?.app_name}`, "_blank")
						  }
					  }}
                    />
                  );

                if (triggers.length > 2) {
                  if (data.action.app_name === "shuffle-subflow") {
                    const parsedImage = triggers[3].large_image;
                    actionimg = (
                      <img
                        alt={"Shuffle Subflow"}
                        src={parsedImage}
                        style={{
                          marginRight: 20,
                          width: imgsize,
                          height: imgsize,
                          border: `2px solid ${statusColor}`,
                          borderRadius: executionData.start === data.action.id ? 25 : 5,
                        }}
                      />
                    );
                  }

                  if (data?.action?.name === "User Input" || data?.action?.app_name === "User Input" || data?.action?.name === "run_userinput") {
                    actionimg = (
                      <img
                        alt={"User Input Trigger"}
                        src={triggers[4].large_image}
                        style={{
                          marginRight: 20,
                          width: imgsize,
                          height: imgsize,
                          borderRadius: executionData.start === data.action.id ? 25 : 5,
                        }}
                      />
                    );
                  }
                }

                if (data.action.app_name === "Shuffle Tools" && data.action.id !== undefined && cy !== undefined) {
                  const nodedata = cy.getElementById(data.action.id).data();
                  //if (nodedata !== undefined && nodedata !== null && nodedata.fillstyle === "linear-gradient") {
                  if (nodedata !== undefined && nodedata !== null) {
                    var imgStyle = {
                      marginRight: 20,
                      width: imgsize,
                      height: imgsize,
                      border: `2px solid ${statusColor}`,
                      borderRadius: executionData.start === data.action.id ? 25 : 5,
                      background: `linear-gradient(to right, ${nodedata.fillGradient})`,
                    };

                    actionimg = (
                      <img
                        alt={nodedata.label}
                        src={nodedata.large_image}
                        style={imgStyle}
                      />
                    );
                  } else {
                    //console.log("Node not found: ", nodedata)
                    actionimg = (
                      <img
                        alt={data.action.app_name}
                        src={data.action.large_image}
                        style={{
                          marginRight: 20,
                          width: imgsize,
                          height: imgsize,
                          border: `2px solid ${statusColor}`,
                          borderRadius: executionData.start === data.action.id ? 25 : 5,
                        }}
                      />
                    )
                  }
                }

                if (validate.valid && typeof validate.result === "string") {
                  validate.result = JSON.parse(validate.result);
                }

                if (validate.valid && typeof validate.result === "object") {
                  if (
                    validate.result.result !== undefined &&
                    validate.result.result !== null
                  ) {
                    try {
                      validate.result.result = JSON.parse(validate.result.result);
                    } catch (e) {
                      //console.log("ERROR PARSING: ", e)
                    }
                  }
                }


                var similarActionsView = null
                if (data.similar_actions !== undefined && data.similar_actions !== null) {
                  var minimumMatch = 85
                  var matching_executions = []
                  if (data.similar_actions !== undefined && data.similar_actions !== null) {
                    for (let [k, kval] in Object.entries(data.similar_actions)) {
                      if (data.similar_actions.hasOwnProperty(k)) {
                        if (data.similar_actions[k].similarity > minimumMatch) {
                          matching_executions.push(data.similar_actions[k].execution_id)
                        }
                      }
                    }
                  }

                  if (matching_executions.length !== 0) {
                    var parsed_url = matching_executions.join(",")

                    similarActionsView =
                      <Tooltip
                        color="primary"
                        title="See executions with similar results (not identical)"
                        placement="top"
                        style={{ zIndex: 50000, marginLeft: 50, }}
                      >
                        <IconButton
                          style={{
                            marginTop: "auto",
                            marginBottom: "auto",
                            height: 30,
                            paddingLeft: 0,
                            width: 30,
                          }}
                          onClick={() => {
                            navigate(`?execution_highlight=${parsed_url}`)
                          }}
                        >
                          <PreviewIcon style={{ color: "rgba(255,255,255,0.5)" }} />
                        </IconButton>
                      </Tooltip>
                  }
                }

                const chosenNodeId = new URLSearchParams(cursearch).get("node");
                const highlightNode = chosenNodeId !== null && chosenNodeId !== undefined && chosenNodeId !== "" && chosenNodeId === data.action.id
				var relevant_errors = []
				if (data?.action?.parameters !== undefined && data?.action?.parameters !== null && data?.action?.parameters.length > 0) {
					for (var i = 0; i < data.action.parameters.length; i++) {
						// Specific error patterns in params
						const param = data.action.parameters[i]
						if (param?.name?.endsWith("_error") && (param?.name?.startsWith("shuffle_") || param?.name?.startsWith("liquid_"))) {
							relevant_errors.push(param)
						}
					}
				}

				if (relevant_errors.length === 0) {
					const foundError = getErrorSuggestion(validate.result)
					if (foundError !== undefined && foundError !== null && foundError !== "") {
						relevant_errors = [foundError]
					}
				}

                return (
                  <div
                    key={index}
                    style={{
                      marginBottom: 20,
                      border: highlightNode ? `2px solid ${red}`
                        :
                        data.action.sub_action === true
                          ? "1px solid rgba(255,255,255,0.3)"
                          : "1px solid rgba(255,255,255, 0.3)",
                      borderRadius: theme.palette?.borderRadius,
                      backgroundColor: theme.palette.cardBackgroundColor,
                      padding: "15px 10px 10px 10px",
                      overflow: "hidden",
                    }}
                    onMouseOver={() => {
                      if (cy == undefined || cy == null) {
                        return
                      }

                      var currentnode = cy.getElementById(data.action.id);
                      if (currentnode !== undefined && currentnode !== null && currentnode.length !== 0) {
                        currentnode.addClass("shuffle-hover-highlight");
                      }

                      // Add a hover highlight

                      //var copyText = document.getElementById(
                      //	"copy_element_shuffle"
                      //)
                    }}
                    onMouseOut={() => {
                      if (cy == undefined || cy == null) {
                        return
                      }

                      var currentnode = cy.getElementById(data.action.id);
                      if (currentnode.length !== 0) {
                        currentnode.removeClass("shuffle-hover-highlight");
                      }
                    }}
                  >
                    <div style={{ display: "flex" }}>
                      <div style={{ display: "flex", marginBottom: 15 }}>
                        <IconButton
                          style={{
                            marginTop: "auto",
                            marginBottom: "auto",
                            height: 30,
                            paddingLeft: 0,
                            width: 30,
                          }}
                          onClick={() => {
                            if (cy !== undefined && cy !== null) {
                              const oldstartnode = cy.getElementById(data.action.id);
                              //console.log("FOUND NODe: ", oldstartnode)
                              if (oldstartnode !== undefined && oldstartnode !== null) {
                                const foundname = oldstartnode.data("label")
                                if (foundname !== undefined && foundname !== null) {
                                  data.action.label = foundname
                                }
                              }

                              //console.log("Click data: ", data)
                              //data.action.label = ""
                              setSelectedResult(data);
                              setActiveDialog("result")
                              setCodeModalOpen(true);
                            } else {
                              //toast("Please wait until the workflow is loaded and try again")
                              setCodeModalOpen(true)
                              setSelectedResult(data)

                            }
                          }}
                        >
                          <Tooltip
                            color="primary"
                            title={
								<Typography variant="body1">
									Expand debug window. Errors: {relevant_errors.length}
								</Typography>
							}
                            placement="top"
                            style={{ zIndex: 50000 }}
                          >
                            <ArrowLeftIcon style={{ 
								color: relevant_errors.length > 0 ? red : theme.palette.textColor,
							}} />
                          </Tooltip>
                        </IconButton>
                        {actionimg}
                        <div>
                          <div
                            style={{
                              fontSize: 24,
                              marginTop: "auto",
                              marginBottom: "auto",
                            }}
                          >
                            <b>{data.action.label === undefined || data.action.label === null || data.action.label === "" ? data.action.label : data.action.label.replaceAll("_", " ")}</b>

                          </div>
                          <div style={{ fontSize: 14 }}>
                            <Typography variant="body2" color="textSecondary">
                              {data.action.name}
                            </Typography>
                          </div>
                        </div>
                      </div>

					  {data.action.app_name === "AI Agent" || data.action.app_name === "Shuffle Agent" ? 
                        <span
                          style={{ flex: 10, float: "right", textAlign: "right" }}
                        >
						  <Tooltip title={"Explore Agent Timeline"}>
							  <a
								rel="noopener noreferrer"
								href={`/agents?execution_id=${executionData.execution_id}&authorization=${executionData.authorization}&node_id=${data.action.id}`}
								target="_blank"
								style={{
								  textDecoration: "none",
								  color: theme.palette.linkColor,
								}}
								onClick={(event) => { }}
							  >
								<OpenInNewIcon />
							  </a>
						  </Tooltip>
						</span>
					  : null}

                      {data?.action?.name === "run_schemaless" || data?.action?.name === "run_singul" || data?.action?.name === "singul" && data?.action?.parameters?.length > 4 ?  
							<div
						  		style={{position: "relative", flex: 10, float: "right", textAlign: "right", }}
							>
							  <Tooltip title={`Explore the raw debug-output: ${data?.action?.parameters?.find((param) => param?.name === "x-debug-url")?.value || ""}`}>
								  <a
									rel="noopener noreferrer"
									href={data?.action?.parameters?.find((param) => param?.name === "x-debug-url")?.value || ""}
									target="_blank"
									style={{
									  textDecoration: "none",
									  color: "rgba(255,255,255,0.4)",
									}}
								  >
									<OpenInNewIcon />
								  </a>
							  </Tooltip>
							  <div style={{position: "absolute", top: 30, right: 0, }}> 
								  <Tooltip title={"Explore in datastore"}>
									  <a
										rel="noopener noreferrer"
										href={`/admin?tab=datastore&category=${data?.action?.parameters?.find((param) => param?.name === "action")?.value || ""}&src=workflow`}
										target="_blank"
										style={{
										  textDecoration: "none",
										}}
									  > 
										<StorageIcon style={{color: "#f85a3e", }} />
									  </a>
								  </Tooltip>
							  </div>
							</div>
						: null}

                      {data.action.app_name === "shuffle-subflow" &&
                        validate.result.success !== undefined &&
                        validate.result.success === true ? (
                        <span
                          style={{ flex: 10, float: "right", textAlign: "right" }}
                        >
                          {validate.valid &&
                            data.action.parameters !== undefined &&
                            data.action.parameters !== null &&
                            data.action.parameters.length > 0 ? (
                            data.action.parameters[0].value ===
                              props.match.params.key ? (
                              <span
                                style={{ cursor: "pointer", color: "#FF8544" }}
                                onClick={(event) => {
                                  getWorkflowExecution(
                                    props.match.params.key,
                                    validate.result.execution_id
                                  );
                                }}
                              >
                                <OpenInNewIcon />
                              </span>
                            ) : (
                              <a
                                rel="noopener noreferrer"
                                href={`/workflows/${data.action.parameters[0].value}?view=executions&execution_id=${validate.result.execution_id}`}
                                target="_blank"
                                style={{
                                  textDecoration: "none",
                                  color: theme.palette.linkColor,
                                }}
                                onClick={(event) => { }}
                              >
                                <OpenInNewIcon />
                              </a>
                            )
                          ) : (
                            ""
                          )}
                        </span>
                      ) : null}
                    </div>

                    {data.status !== "SUCCESS" ?
                      <div style={{ marginBottom: 5, display: "flex" }}>
                        <Typography variant="body1">
                          <b>Status&nbsp;</b>
                        </Typography>
                        <Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
                          {data.status}
                        </Typography>
                        {similarActionsView}
                      </div>
                      : null}

                    {validate.valid ? (
                      <span>
                        <ReactJson
                          src={validate.result}
                          theme={theme.palette.jsonTheme}
                          style={theme.palette.reactJsonStyle}
                          shouldCollapse={(jsonField) => {
                            return collapseField(jsonField)
                          }}
                          iconStyle={theme.palette.jsonIconStyle}
                          collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
                          displayArrayKey={false}
                          enableClipboard={(copy) => {
                            handleReactJsonClipboard(copy);
                          }}
                          displayDataTypes={false}
                          onSelect={(select) => {
                            HandleJsonCopy(showResult, select, data.action.label);
                            console.log("SELECTED!: ", select);
                          }}
                          name={`$${data?.action?.label?.toLowerCase()}`}
                        />

                      </span>
                    ) : (
                      <div
                        style={{
                          maxHeight: 250,
                          overflowX: "hidden",
                          overflowY: "auto",
                          whiteSpace: "pre-wrap",
                        }}
                      >
						{validate?.result?.length > 0 ? 
							<div>
								<Typography
								  variant="body1"
								  style={{}}
								>
								  <b>Result</b>&nbsp;
								</Typography>
								<Typography
								  variant="body1"
								  color="textSecondary"
								  style={{ display: "inline-block" }}
								>
								  {data.result}
								</Typography>
							</div>
						: null}
                      </div>
                    )}
                  </div>
                );
              })
            )}
        </div>
      )}
    </Drawer>
  );

  // This sucks :)
  const curapp = !codeModalOpen
    ? {}
    : selectedResult.action.app_name === "shuffle-subflow"
      ? triggers[2]
      : selectedResult.action.app_name === "User Input"
        ? triggers[3]
        : apps.find(
          (a) =>
            a.name === selectedResult.action.app_name &&
            a.app_version === selectedResult.action.app_version
        );
  const imgsize = 50;
  const statusColor = !codeModalOpen
    ? "red"
    : selectedResult.status === "FINISHED" ||
      selectedResult.status === "SUCCESS"
      ? green
      : selectedResult.status === "ABORTED" || selectedResult.status === "FAILURE"
        ? "red"
        : yellow;

  const validate = !codeModalOpen ? "" : validateJson(selectedResult.result.trim())
  if (validate.valid && typeof validate.result === "string") {
    validate.result = JSON.parse(validate.result)
  }

  const AppResultVariable = ({ data, action }) => {
    const [open, setOpen] = React.useState(false)
    const showVariable = data.value.length < 60

    // Check if it's valid JSON
    const checked = validateJson(data.value.trim())

    if (data.name === "shuffle_action_logs" && data.value !== undefined && data.value !== null && data.value.length > 0 && data.value.includes("add env SHUFFLE_LOGS_DISABLED")) {

		if (isCloud) {
			return null
		}

		  return (
			<div style={{ maxWidth: 600, marginTop: 75, overflowX: "hidden", }}>
			  <Typography
				variant="body1"
				style={{}}
			  >
				<b>Action Logs</b>
			  </Typography>
			  <Typography variant="body2" color="textSecondary" style={{ whiteSpace: 'pre-line', }}>
				More log details for this action are not available without <a style={{ color: theme.palette.linkColor, }} href="/admin?tab=locations" target="_blank" rel="noopener noreferrer">an onprem environment</a> with the <a style={{ color: theme.palette.linkColor, }} href="/docs/configuration#scaling-shuffle" target="_blank" rel="noopener noreferrer">SHUFFLE_LOGS_DISABLED</a> environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode.
			  </Typography>
			</div>
		  )
    }

    var showlink = false
    if (data.name.endsWith("-Url")) {
      //data.name = data.name.toLowerCase().replaceAll("-", "_")
      if (data.value.startsWith(", ")) {
        data.value = data.value.substring(2)
      }

      if (data.value.startsWith("http") || (data.value.startsWith("/") && data.value.includes("?"))) {
        showlink = true
      }
    }

    return (
      <div style={{ maxWidth: 600, overflowX: "hidden", }}>
        {data.value.length > 60 || checked.valid ?
		  <span>
          <IconButton
            style={{
              marginBottom: 0,
              marginTop: 5,
              cursor: "pointer",
              border: "1px solid rgba(255,255,255,0.3)",
              borderRadius: theme.palette?.borderRadius,
              padding: "3px 8px 3px 8px",
            }}
            onClick={() => {
              if (!showVariable) {
                setOpen(!open)
              }
            }}
          >
            <Typography
              variant="body1"
              style={{}}
            >
              <b>{data.name}</b>
              {checked.valid ?
                <Chip
                  style={{ marginLeft: 10, padding: 0, cursor: "pointer", }}
                  label={"JSON"}
                  variant="outlined"
                  color="secondary"
                />
                : null}
              {showVariable ? data.value : null}
            </Typography>
          </IconButton>
		  <IconButton
			aria-label="Copy webhook"
			style={{marginLeft: 10, }}
			onClick={() => {
			  var copyText = document.getElementById("copy_element_shuffle");
			  if (copyText !== undefined && copyText !== null) {
				const clipboard = navigator.clipboard;
				if (clipboard === undefined) {
				  toast("Can only copy over HTTPS (port 3443)");
				  return;
				}

				navigator.clipboard.writeText(data.value);
				copyText.select();
				copyText.setSelectionRange(
				  0,
				  99999
				); /* For mobile devices */

				/* Copy the text inside the text field */
				document.execCommand("copy");
				toast("Copied to clipboard");
			  } else {
				console.log("Couldn't find the copy field: ", copyText);
			  }
			}}
			edge="end"
		  >
			<ContentCopyIcon style={{heigth: 15, }}/>
		  </IconButton>
		  </span>
          :
          <Typography
            variant="body1"
            style={{}}
          >
            <b>{data.name}</b>: <span style={{color: "rgba(255,255,255,0.5)" }}>
				{showVariable ? data.value : null}
			</span>
          </Typography>
        }
        {open ?
          checked.valid ?
            <ReactJson
              src={checked.result}
              theme={theme.palette.jsonTheme}
              style={theme.palette.reactJsonStyle}
              shouldCollapse={(jsonField) => {
                return collapseField(jsonField)
              }}
              iconStyle={theme.palette.jsonIconStyle}
              collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
              displayArrayKey={false}
              displayDataTypes={false}
              name={"Parsed data for variable " + data.name}
            />
            :
            <Typography
              variant="body2"
              style={{
				marginTop: 5, 
                whiteSpace: 'pre-wrap',
                cursor: showlink ? "pointer" : "default",
              }}
              onClick={(e) => {
                if (showlink) {
                  e.preventDefault()
                  e.stopPropagation()
                  window.open(data.value, "_blank")
                }
              }}
              color={showlink ? "#ff8544" : "textSecondary"}
            >
              {data.value}
            </Typography>
          : null}
      </div>
    )
  }

  var draggingDisabled = false; 

  const currentSuggestion = getErrorSuggestion(validate.result)
  const codePopoutModal = !codeModalOpen ? null : (
    <Dialog
      PaperComponent={PaperComponent}
      aria-labelledby="draggable-dialog-title"
      disableEnforceFocus={true}
      style={{ pointerEvents: "none", zIndex: activeDialog === "result" ? 1200 : 1100, }}
      hideBackdrop={true}
      open={codeModalOpen}
      PaperProps={{
        onClick: () => setActiveDialog("result"),
        sx: {
          pointerEvents: "auto",
          color: theme.palette.text.primary,
          minWidth: isMobile ? "90%" : 900,
		  minHeight: 500, 
          maxHeight: 650,
          overflowY: "auto",
          overflowX: "hidden",
          border: theme.palette.defaultBorder,

          borderRadius: theme.palette.DialogStyle.borderRadius,
          backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
          '& .MuiDialogContent-root': {
            backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
            padding: "30px 30px 30px 30px",
          },
        },
      }}
    >
      <span id="top_bar" style={{ position: "sticky", top: -20, zIndex: 12000,}}>
        <Tooltip
          title="Suggest solution"
          placement="top"
          style={{ zIndex: 50000 }}
        >
          <IconButton
            disabled
            style={{
              zIndex: 5000,
              position: "absolute",
              top: 4,
              right: 210,
            }}
            onClick={(e) => {
              e.preventDefault()
            }}
          >
            <AutoFixHighIcon />
          </IconButton>
        </Tooltip>
        <Tooltip
          title="Find successful execution"
          placement="top"
          style={{ zIndex: 50000 }}
        >
          <IconButton
            style={{
              zIndex: 5000,
              position: "absolute",
              top: 4,
              right: 170,
            }}
            onClick={(e) => {
              e.preventDefault()

              if (workflowExecutions !== null) {
                for (let execkey in workflowExecutions) {
                  const execution = workflowExecutions[execkey];
                  if (execution.execution_argument.includes("too large")) {
                    continue
                  }

                  const result = execution.results.find((data) => data.status === "SUCCESS" && data.action.id === selectedResult.action.id)

                  if (result !== undefined) {
                    const oldstartnode = cy.getElementById(selectedResult.action.id)
                    if (oldstartnode !== undefined && oldstartnode !== null) {
                      const foundname = oldstartnode.data("label")
                      if (foundname !== undefined && foundname !== null) {
                        result.action.label = foundname
                      }
                    }

                    setSelectedResult(result)
                    setUpdate(Math.random())
                    break;
                  }
                }
              }
            }}
          >
            <DoneIcon style={{ color: theme.palette.text.primary }} />
          </IconButton>
        </Tooltip>
        <Tooltip
          title="Find failed execution"
          placement="top"
          style={{ zIndex: 50000 }}
        >
          <IconButton
            style={{
              zIndex: 5000,
              position: "absolute",
              top: 4,
              right: 136,
            }}
            onClick={(e) => {
              e.preventDefault();
              for (let execkey in workflowExecutions) {
                const execution = workflowExecutions[execkey];
                const result = execution.results.find(
                  (data) =>
                    data.action.id === selectedResult.action.id &&
                    data.status !== "SUCCESS" &&
                    data.status !== "SKIPPED" &&
                    data.status !== "WAITING"
                );

                if (result !== undefined) {
                  const oldstartnode = cy.getElementById(selectedResult.action.id);
                  if (oldstartnode !== undefined && oldstartnode !== null) {
                    const foundname = oldstartnode.data("label")
                    if (foundname !== undefined && foundname !== null) {
                      result.action.label = foundname
                    }
                  }

                  setSelectedResult(result);
                  setUpdate(Math.random());
                  break;
                }
              }
            }}
          >
            <ErrorIcon style={{ color: theme.palette.text.primary }} />
          </IconButton>
        </Tooltip>
        <Tooltip
          title="Explore execution"
          placement="top"
          style={{ zIndex: 10011 }}
        >
          <IconButton
            style={{ zIndex: 5000, position: "absolute", top: 4, right: 98 }}
            onClick={(e) => {
              e.preventDefault();
              const executionIndex = workflowExecutions.findIndex((data) => data.execution_id === selectedResult.execution_id);

              if (executionIndex !== -1) {
                setExecutionModalOpen(true);
                setExecutionModalView(1);

                if (workflowExecutions[executionIndex] !== undefined && workflowExecutions[executionIndex] !== null && workflowExecutions[executionIndex].execution_argument.includes("too large")) {
                  //checkStarted = true 
                  setExecutionData({});
                  start();
                  setExecutionRunning(true);
                  setExecutionRequestStarted(false);
                } else {
                  setExecutionData(workflowExecutions[executionIndex]);
                }
              }
            }}
          >
            <VisibilityIcon style={{ color: theme.palette.text.primary }} />
          </IconButton>
        </Tooltip>
        <Tooltip
          title="Move window"
          placement="top"
          style={{ zIndex: 10011 }}
        >
          <IconButton
            id="draggable-dialog-title"
            style={{
              zIndex: 5000,
              position: "absolute",
              top: 4,
              right: 34,
              cursor: "move",
            }}
            onClick={(e) => {
            }}
          >
            <DragIndicatorIcon style={{ color: theme.palette.text.primary }} />
          </IconButton>
        </Tooltip>
        <Tooltip
          title="Close window"
          placement="top"
          style={{ zIndex: 10011 }}
        >
          <IconButton
            style={{ zIndex: 5000, position: "absolute", top: 4, right: 4, }}
            onClick={(e) => {
              e.preventDefault();
              setCodeModalOpen(false);
            }}
          >
            <CloseIcon style={{ color: theme.palette.text.primary }} />
          </IconButton>
        </Tooltip>
      </span>

      <DialogContent>
      <div style={{ marginBottom: 40, }}>
        <div style={{ display: "flex", marginBottom: 15, position: "sticky", top: -31, zIndex: 10000, }}>
          {curapp === null ? null : (
            <img
              alt={selectedResult.action.app_name}
              src={selectedResult === undefined ? theme.palette.defaultImage : selectedResult?.action?.name === "run_userinput" ? triggers[4].large_image : selectedResult.action.app_name === "shuffle-subflow" ? triggers[3].large_image : selectedResult.action !== undefined && selectedResult.action.large_image !== undefined && selectedResult.action.large_image !== null && selectedResult.action.large_image !== "" ? selectedResult.action.large_image : curapp !== undefined ? curapp.large_image : theme.palette.defaultImage}
              style={{
                marginRight: 20,
                width: imgsize,
                height: imgsize,
                border: `2px solid ${statusColor}`,
                filter: curapp === undefined ? "grayscale(100%)" : null,
                borderRadius: theme.palette?.borderRadius,
              }}
            />
          )}

          <div>
            <div
              id="draggable-dialog-title"
              style={{
                fontSize: 24,
                marginTop: "auto",
                marginBottom: "auto",
              }}
            >
              <b>{selectedResult.action.label.replaceAll("_", " ")}</b>
            </div>
            <Typography variant="body2" color="textSecondary" style={{ }}>
	  			{selectedResult.action.name}
			</Typography>
          </div>
        </div>


        {currentSuggestion.length > 0 ?
          <div style={{ marginBottom: 5 }}>
            <b style={{ color: "rgba(214,110,117)", }}>Debug:</b> {currentSuggestion}
          </div>
          :
          <div style={{ marginBottom: 5 }}>
            <b>Status </b> {selectedResult.status}
          </div>
        }

        {validate.valid ? (
          <ReactJson
            src={validate.result}
            theme={theme.palette.jsonTheme}
            style={theme.palette.reactJsonStyle}
            shouldCollapse={(jsonField) => {
              return collapseField(jsonField)
            }}
            iconStyle={theme.palette.jsonIconStyle}
            collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
            displayArrayKey={false}
            enableClipboard={(copy) => {
              handleReactJsonClipboard(copy);
            }}
            displayDataTypes={false}
            onSelect={(select) => {
              HandleJsonCopy(validate.result, select, selectedResult.action.label);
            }}
            name={"Results for " + selectedResult.action.label}
          />
        ) : (
          <div>
			{validate?.result?.length > 0 ?
				<Typography>
					<b>Result</b>
				</Typography>
			: null}
            <br />
            <span
              style={{
                wordBreak: "break-word",
                display: "inline-block",
                whiteSpace: "pre-wrap",
              }}

              onClick={() => {
                to_be_copied = selectedResult.result;
                var copyText = document.getElementById(
                  "copy_element_shuffle"
                );

                if (copyText !== null && copyText !== undefined) {
                  const clipboard = navigator.clipboard;
                  if (clipboard === undefined) {
                    toast("Can only copy over HTTPS (port 3443)");
                    return;
                  }

                  navigator.clipboard.writeText(to_be_copied);

                  copyText.select();
                  copyText.setSelectionRange(
                    0,
                    99999
                  ); /* For mobile devices */

                  /* Copy the text inside the text field */
                  document.execCommand("copy");
                } else {
                  console.log(
                    "Failed to copy. copy_element_shuffle is undefined"
                  );
                }
              }}
            >
              {selectedResult.result}
            </span>
          </div>
        )}

        <div>
          {selectedResult.action.parameters !== null &&
            selectedResult.action.parameters !== undefined ? (
            <div>
              <Divider
                style={{
                  backgroundColor: theme.palette.surfaceColor,
                  marginTop: 15,
                  marginBottom: 15,
                }}
              />
              <Typography
                variant="h6"
                style={{ marginBottom: 0, marginTop: 0 }}
              >
                Variable & Debug info <span style={{ fontSize: 10 }}>({selectedResult?.action?.parameters?.length})</span>
              </Typography>
              {selectedResult?.action?.parameters?.map((data, index) => {
                if (data.value.length === 0) {
                  return null;
                }

                if (
                  data.example !== undefined &&
                  data.example !== null &&
                  data.example.includes("***")
                ) {
                  return null;
                }

                return (
                  <AppResultVariable key={index} data={data} action={selectedResult.action} />
                );
              })}
            </div>
          ) : null}
        </div>
      </div>
      </DialogContent>
    </Dialog>
  );

  const newView = (
    <div style={{ color: theme.palette.text.primary, marginLeft: leftSideBarOpenByClick ? workflow?.public ? 210 : 250 : workflow?.public ? isLoggedIn ? 60 : 0 : isLoggedIn ? 100 : 0, transition: "margin-left 0.3s ease", overflow: 'hidden' }}>
      <div
        style={{ display: "flex", borderTop: "0px solid rgba(91, 96, 100, 1)" }}
      >
        {/*isMobile ? null : leftView*/}
        {leftView}
        
        {userdata.support === true && (
        <div style={{ position: "relative" }}>
          <div
            onClick={() => {
              console.log("Toggle sidebar!")
              setLeftBarSize(leftViewOpen ? 0 : 235)
              setLeftViewOpen(!leftViewOpen)
            }}
            style={{
              position: "absolute",
              left: 0,
              top: "50%",
              transform: "translateY(-50%)",
              zIndex: 1000,
              backgroundColor: theme.palette.surfaceColor,
              border: `1px solid ${theme.palette.defaultBorder}`,
              borderLeft: "none",
              borderTopRightRadius: "8px",
              borderBottomRightRadius:"8px",
              borderTopLeftRadius: "0",
              borderBottomLeftRadius: "0",
              width: leftViewOpen ? 24 : 32,
              height: 48,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            transition: "left 0.3s ease, width 0.3s ease, border-radius 0.3s ease",
            cursor: "pointer",
          }}
        >
          {leftViewOpen ? 
            <ArrowLeftIcon fontSize="small" style={{ color: theme.palette.text.primary }} /> : 
            <ArrowRightIcon fontSize="small" style={{ color: theme.palette.text.primary }} />
          }
          </div>
        </div>
        )}
        {workflow.id === undefined || workflow.id === null || appsLoaded === false ? (
          <div
            style={{
              width: bodyWidth - leftBarSize - 15,
              height: 150,
              textAlign: "center",
            }}
          >
			{isLoaded && workflowDone ? 
				<div style={{marginTop: "30vh", }}>
					{/*
					<Typography variant="body1" color="textSecondary">
						No workflow to load. Workflow runs may still exist. If you think this is wrong, please contact support@shuffler.io
					</Typography>
					*/}
				</div>
			:
				<div>
					<CircularProgress
					  style={{
						marginTop: "30vh",
						height: 35,
						width: 35,
						marginLeft: "auto",
						marginRight: "auto",
					  }}
					/>
					<Typography variant="body1" color="textSecondary">
					  Loading Workflow
					</Typography>
				</div>
			}
          </div>
        ) : (
          <span>
            {/*<Fade in={true} timeout={1000} style={{ transitionDelay: `${150}ms` }}>*/}
            <CytoscapeComponent
              elements={elements}
              minZoom={0.35}
              maxZoom={2.0}
              userZoomingEnabled={false}
              style={{
                width: workflow?.public && !isLoggedIn ? cytoscapeWidth + 50 : cytoscapeWidth,
                height: workflow?.public && !isLoggedIn ? (bodyHeight - 50) : bodyHeight - 2,
                backgroundColor: theme.palette.cytoscapeBackgroundColor,
              }}
              stylesheet={cystyle}
              boxSelectionEnabled={true}
              autounselectify={false}
              showGrid={true}
              id="cytoscape_view"
              cy={(incy) => {
                // FIXME: There's something specific loading when
                // you do the first hover of a node. Why is this different?

                // Custom wheel handler for sensitivity for same zoom in and zoom out effect with mouse and touch pad
                const container = incy.container();
                if (container && !container._customWheelHandler) {
                  container._customWheelHandler = true;
                  container.addEventListener('wheel', (e) => {
                    e.preventDefault();
                    const sensitivity = 0.25;
                    
                    const zoomFactor = Math.pow(10, -e.deltaY * sensitivity / 500);
                    const currentZoom = incy.zoom();
                    let newZoom = currentZoom * zoomFactor;
                    
                    newZoom = Math.max(0.35, Math.min(2.0, newZoom));
                    
                    // Zoom towards cursor position
                    const rect = container.getBoundingClientRect();
                    const position = {
                      x: e.clientX - rect.left,
                      y: e.clientY - rect.top
                    };
                    
                    incy.zoom({
                      level: newZoom,
                      renderedPosition: position
                    });
                  }, { passive: false });
                }

                setCy(incy);
              }}
            />
          </span>
        )}
      </div>
      {executionModal}

      <RightSideBar />

      {/* New parsedaction in new release */}
      <Drawer 
        anchor="right"
        open={
          Object.getOwnPropertyNames(selectedAction).length > 0 &&
          rightSideBarOpen &&
          Object.getOwnPropertyNames(selectedTrigger || {}).length === 0 && 
          Object.getOwnPropertyNames(selectedComment || {}).length === 0 
        }
        hideBackdrop={true}
        transitionDuration={{ enter: 180, exit: 0 }}
        onClose={() => {
          setRightSideBarOpen(false);
        }}
        ModalProps={{
          keepMounted: true,
        }}
        sx={{
          pointerEvents: "none",
          "& .MuiDrawer-paper": {
            pointerEvents: "auto",
          },
        }}
        PaperProps={{
          style: {
            width: rightsidebarStyle.width,
            minWidth: rightsidebarStyle.minWidth,
            maxWidth: rightsidebarStyle.maxWidth,
            overflow: "hidden",
          },
        }}
      >
          <ParsedAction
            id="rightside_subactions"
            files={files}
            isCloud={isCloud}
            getParents={getParents}
            toolsAppId={toolsApp.id}
            setShowVideo={setShowVideo}
            actionDelayChange={actionDelayChange}
            getAppAuthentication={getAppAuthentication}
            appAuthentication={appAuthentication}
            authenticationType={authenticationType}
            scrollConfig={scrollConfig}
            setScrollConfig={setScrollConfig}
            selectedAction={selectedAction}
            workflow={workflow}
            setWorkflow={setWorkflow}
            setSelectedAction={setSelectedAction}
            setUpdate={setUpdate}
            selectedApp={selectedApp}
            workflowExecutions={workflowExecutions}
            setSelectedResult={setSelectedResult}
            setSelectedApp={setSelectedApp}
            setSelectedTrigger={setSelectedTrigger}
            setSelectedEdge={setSelectedEdge}
            setCurrentView={setCurrentView}
            cy={cy}
            setAuthenticationModalOpen={setAuthenticationModalOpen}
            setVariablesModalOpen={setVariablesModalOpen}
            setLastSaved={setLastSaved}
            setCodeModalOpen={setCodeModalOpen}
            selectedNameChange={selectedNameChange}
            rightsidebarStyle={rightsidebarStyle}
            showEnvironment={showEnvironment}
            selectedActionEnvironment={selectedActionEnvironment}
            environments={environments}
            setNewSelectedAction={setNewSelectedAction}
            sortByKey={sortByKey}
            appApiViewStyle={appApiViewStyle}
            globalUrl={globalUrl}
            setSelectedActionEnvironment={setSelectedActionEnvironment}
            requiresAuthentication={requiresAuthentication}
            setRequiresAuthentication={setRequiresAuthentication}
            setLastSaved={setLastSaved}
            lastSaved={lastSaved}
            aiSubmit={aiSubmit}
            listCache={listCache}
            setActiveDialog={setActiveDialog}
            apps={apps}
            expansionModalOpen={codeEditorModalOpen}
            setExpansionModalOpen={setCodeEditorModalOpen}
            setEditorData={setEditorData}
            setAiQueryModalOpen={setAiQueryModalOpen}
            fixExample={fixExample}
            suborgWorkflows={suborgWorkflows}
            originalWorkflow={originalWorkflow}
            runFromHere={runFromHere}
          />
        </Drawer>


      {/* old code just for backup */}
      {/* {
        rightSideBarOpen && Object.getOwnPropertyNames(selectedAction).length > 0 ?
          <Fade in={true} timeout={250} style={{ transitionDelay: `${0}ms` }}>
            <div id="rightside_actions" style={rightsidebarStyle}>
              <ParsedAction
                id="rightside_subactions"
                files={files}
                isCloud={isCloud}
                getParents={getParents}
                toolsAppId={toolsApp.id}
                setShowVideo={setShowVideo}
                actionDelayChange={actionDelayChange}
                getAppAuthentication={getAppAuthentication}
                appAuthentication={appAuthentication}
                authenticationType={authenticationType}
                scrollConfig={scrollConfig}
                setScrollConfig={setScrollConfig}
                selectedAction={selectedAction}
                workflow={workflow}
                setWorkflow={setWorkflow}
                setSelectedAction={setSelectedAction}
                setUpdate={setUpdate}
                selectedApp={selectedApp}
                workflowExecutions={workflowExecutions}
                setSelectedResult={setSelectedResult}
                setSelectedApp={setSelectedApp}
                setSelectedTrigger={setSelectedTrigger}
                setSelectedEdge={setSelectedEdge}
                setCurrentView={setCurrentView}
                cy={cy}
                setAuthenticationModalOpen={setAuthenticationModalOpen}
                setVariablesModalOpen={setVariablesModalOpen}
                setLastSaved={setLastSaved}
                setCodeModalOpen={setCodeModalOpen}
                selectedNameChange={selectedNameChange}
                rightsidebarStyle={rightsidebarStyle}
                showEnvironment={showEnvironment}
                selectedActionEnvironment={selectedActionEnvironment}
                environments={environments}
                setNewSelectedAction={setNewSelectedAction}
                sortByKey={sortByKey}
                appApiViewStyle={appApiViewStyle}
                globalUrl={globalUrl}
                setSelectedActionEnvironment={setSelectedActionEnvironment}
                requiresAuthentication={requiresAuthentication}
                setRequiresAuthentication={setRequiresAuthentication}
                setLastSaved={setLastSaved}
                lastSaved={lastSaved}
                aiSubmit={aiSubmit}
                listCache={listCache}
                setActiveDialog={setActiveDialog}
                apps={apps}
                expansionModalOpen={codeEditorModalOpen}
                setExpansionModalOpen={setCodeEditorModalOpen}
                setEditorData={setEditorData}
                setAiQueryModalOpen={setAiQueryModalOpen}
                fixExample={fixExample}

		  		suborgWorkflows={suborgWorkflows}
		  		originalWorkflow={originalWorkflow}
  				runFromHere={runFromHere} 
              />
            </div>
          </Fade>
          : null
      } */}
      {/* Looks for triggers" */}
      {/* Only fixed the ones that require scrolling on a small screen */}
      {/* Most important: Actions. But these are a lot more complex */}
      {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT" || selectedTrigger.trigger_type === "SUBFLOW") ?
        <div id="rightside_actions" style={rightsidebarStyle}>
          {Object.getOwnPropertyNames(selectedTrigger)?.length > 0 ?
            selectedTrigger.trigger_type === "SCHEDULE" ?
              ScheduleSidebar
              : selectedTrigger.trigger_type === "PIPELINE" ?
                PipelineSidebar
                : selectedTrigger.trigger_type === "WEBHOOK" ?
                  WebhookSidebar
                  : selectedTrigger.trigger_type === "USERINPUT" ?
                    UserinputSidebar
                    : selectedTrigger.trigger_type === "SUBFLOW" ?
                      SubflowSidebar
                      : null
            : null}
        </div>
        : null}
      {/* 
    {
      rightSideBarOpen && selectedTrigger?.trigger_type === "SUBFLOW"&& Object.getOwnPropertyNames(selectedTrigger)?.length > 0   ? 
      <div id="rightside_actions" style={rightsidebarStyle}>
        <SubflowSidebar/>
        </div> : null
    } */}

      {/*
      <RightSideBar
        scrollConfig={scrollConfig}
        setScrollConfig={setScrollConfig}
        selectedAction={selectedAction}
        workflow={workflow}
        setWorkflow={setWorkflow}
        setSelectedAction={setSelectedAction}
        setUpdate={setUpdate}
        selectedApp={selectedApp}
        workflowExecutions={workflowExecutions}
        setSelectedResult={setSelectedResult}
        setSelectedApp={setSelectedApp}
        setSelectedTrigger={setSelectedTrigger}
        setSelectedEdge={setSelectedEdge}
        setCurrentView={setCurrentView}
        cy={cy}
        setAuthenticationModalOpen={setAuthenticationModalOpen}
        setVariablesModalOpen={setVariablesModalOpen}
        setLastSaved={setLastSaved}
        setCodeModalOpen={setCodeModalOpen}
        selectedNameChange={selectedNameChange}
        rightsidebarStyle={rightsidebarStyle}
        showEnvironment={showEnvironment}
        selectedActionEnvironment={selectedActionEnvironment}
        environments={environments}
        setNewSelectedAction={setNewSelectedAction}
        sortByKey={sortByKey}
        appApiViewStyle={appApiViewStyle}
        globalUrl={globalUrl}
        setSelectedActionEnvironment={setSelectedActionEnvironment}
        requiresAuthentication={requiresAuthentication}
      />
	  */}

      {showWorkflowRevisions ? null :
        <span>
          {/*<BottomAvatars />*/}
          {shownErrors}
          <BottomCytoscapeBar />
          <TopCytoscapeBar />
          {/* <RightsideBar /> */}
        </span>
      }
    </div>
  );


  const ExecutionVariableModal = (props) => {
    const { variableInfo } = props

    const [newVariableName, setNewVariableName] = React.useState(variableInfo.name !== undefined ? variableInfo.name : "");
    const [newVariableDescription, setNewVariableDescription] = React.useState(variableInfo.description !== undefined ? variableInfo.description : "");
    const [newVariableValue, setNewVariableValue] = React.useState(variableInfo.value !== undefined ? variableInfo.value : "");

    if (!executionVariablesModalOpen) {
      return null
    }

    return (
      <Dialog
        open={executionVariablesModalOpen}
        PaperComponent={PaperComponent}
        hideBackdrop={true}
        disableEnforceFocus={true}
        disableBackdropClick={true}
        style={{ pointerEvents: "none" }}
        aria-labelledby="draggable-dialog-title"
        onClose={() => {
          setNewVariableName("");
          setNewVariableValue("");

          setExecutionVariablesModalOpen(false);
        }}
        PaperProps={{
          style: {
            pointerEvents: "auto",
            color: theme.palette.text.primary,
            border: theme.palette.defaultBorder,
            maxWidth: isMobile ? bodyWidth - 100 : 800,
            minWidth: isMobile ? bodyWidth - 100 : 800,

            borderRadius: theme.palette.borderRadius,
            backgroundColor: "black",
          },
        }}
      >
        <FormControl>
          <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
            <span style={{ color: theme.palette.text.primary }}>Runtime Variable</span>
          </DialogTitle>
          <DialogContent>
            Runtime Variables are TEMPORARY variables that you can only be set
            and used during execution. Learn more{" "}
            <a
              rel="noopener noreferrer"
              href="https://shuffler.io/docs/workflows#execution_variables"
              target="_blank"
              style={{ textDecoration: "none", color: theme.palette.linkColor }}
            >
              here
            </a>
            <TextField
              onBlur={(event) => setNewVariableName(event.target.value)}
              color="primary"
              placeholder="Name"
              style={{ marginTop: 25 }}
              InputProps={{
                style: {
                  color: theme.palette.text.primary,
                },
              }}
              margin="dense"
              label="Name"
              fullWidth
              defaultValue={newVariableName}
            />
            <TextField
              onBlur={(event) => setNewVariableValue(event.target.value)}
              color="primary"
              placeholder="Default Value (optional)"
              style={{ marginTop: 25 }}
              InputProps={{
                style: {
                  color: theme.palette.text.primary,
                },
              }}
              margin="dense"
              label="Default Value (optional)"
              fullWidth
              defaultValue={newVariableValue}
            />
          </DialogContent>
          <DialogActions>
            <Button
              style={{ borderRadius: "0px" }}
              onClick={() => {
                setNewVariableName("");
                setNewVariableValue("");
                setExecutionVariablesModalOpen(false);
              }}
              color="primary"
            >
              Cancel
            </Button>
            <Button
              style={{ borderRadius: "0px" }}
              disabled={newVariableName.length === 0}
              variant="contained"
              onClick={() => {
                if (
                  workflow.execution_variables === undefined ||
                  workflow.execution_variables === null
                ) {
                  workflow.execution_variables = [];
                }

                // try to find one with the same name
                const found = workflow.execution_variables.findIndex(
                  (data) => data.name === newVariableName
                );
                //console.log(found)
                if (found !== -1) {
                  if (newVariableName.length > 0) {
                    workflow.execution_variables[found].name = newVariableName;
                  }

                  if (newVariableValue.length > 0) {
                    workflow.execution_variables[found].value = newVariableValue;
                  }
                } else {
                  workflow.execution_variables.push({
                    name: newVariableName,
                    value: newVariableValue,
                    description: "An execution variable",
                    id: uuidv4(),
                  });
                }

                setExecutionVariablesModalOpen(false);
                setNewVariableName("");
                setWorkflow(workflow);
              }}
              color="primary"
            >
              Submit
            </Button>
          </DialogActions>
          {workflowExecutions.length > 0 ? (
            <DialogContent>
              <Divider
                style={{
                  backgroundColor: theme.palette.text.primary,
                  marginTop: 15,
                  marginBottom: 15,
                }}
              />
              <b style={{ marginBottom: 10 }}>Values from last 3 executions</b>
              {workflowExecutions.slice(0, 3).map((execution, index) => {
                if (
                  execution.execution_variables === undefined ||
                  execution.execution_variables === null ||
                  execution.execution_variables === 0
                ) {
                  return null;
                }

                const variable = execution.execution_variables.find(
                  (data) => data.name === newVariableName
                )
                if (variable === undefined || variable.value === undefined) {
                  return null;
                }

                return (
                  <div>
                    {index + 1}: {variable.value}
                  </div>
                );
              })}
            </DialogContent>
          ) : null}
        </FormControl>
      </Dialog>
    )
  }

  const VariablesModal = (props) => {
    const { setVariableInfo, variableInfo } = props

    const [newVariableName, setNewVariableName] = React.useState(variableInfo.name !== undefined ? variableInfo.name : "");
    const [newVariableDescription, setNewVariableDescription] = React.useState(variableInfo.description !== undefined ? variableInfo.description : "");
    const [newVariableValue, setNewVariableValue] = React.useState(variableInfo.value !== undefined ? variableInfo.value : "");

    if (!variablesModalOpen) {
      return null
    }

    return (
      <Dialog
        PaperComponent={PaperComponent}
        aria-labelledby="draggable-dialog-title"
        hideBackdrop={true}
        disableEnforceFocus={true}
        disableBackdropClick={true}
        style={{ pointerEvents: "none" }}
        open={variablesModalOpen}
        onClose={() => {
          setNewVariableName("");
          setNewVariableDescription("");
          setNewVariableValue("");
          setVariablesModalOpen(false);
        }}
        PaperProps={{
          style: {
            pointerEvents: "auto",
            color: theme.palette.text.primary,
            border: theme.palette.defaultBorder,
            maxWidth: isMobile ? bodyWidth - 100 : "100%",

            borderRadius: theme.palette.borderRadius,
            backgroundColor: "black",
          },
        }}
      >
        <FormControl>
          <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
            <span style={{ color: theme.palette.text.primary }}>Workflow Variable</span>
          </DialogTitle>
          <DialogContent>
            <TextField
              onBlur={(event) => setNewVariableName(event.target.value)}
              color="primary"
              placeholder="Name"
              InputProps={{
                style: {
                  color: theme.palette.text.primary,
                },
              }}
              margin="dense"
              fullWidth
              defaultValue={newVariableName}
            />
            <TextField
              onBlur={(event) => setNewVariableDescription(event.target.value)}
              color="primary"
              placeholder="Description"
              margin="dense"
              fullWidth
              InputProps={{
                style: {
                  color: theme.palette.text.primary,
                },
              }}
              defaultValue={newVariableDescription}
            />
            <TextField
              onChange={(event) => setNewVariableValue(event.target.value)}
              rows="6"
              multiline
              color="primary"
              placeholder="Value"
              margin="dense"
              InputProps={{
                style: {
                  color: theme.palette.text.primary,
                },
              }}
              fullWidth
              defaultValue={newVariableValue}
            />
          </DialogContent>
          <DialogActions>
            <Button
              style={{ borderRadius: "0px" }}
              onClick={() => {
                setNewVariableName("");
                setNewVariableDescription("");
                setNewVariableValue("");
                setVariablesModalOpen(false);
              }}
              color="primary"
            >
              Cancel
            </Button>
            <Button
              style={{ borderRadius: "0px" }}
              disabled={
                newVariableName.length === 0 || newVariableValue.length === 0
              }
              variant={"contained"}
              onClick={() => {
                var handled = false
                if (
                  workflow.workflow_variables === undefined ||
                  workflow.workflow_variables === null
                ) {
                  workflow.workflow_variables = [];
                } else {
                  if (variableInfo.index !== undefined && variableInfo.index !== null && variableInfo.index >= 0) {
                    if (newVariableName.length > 0) {
                      workflow.workflow_variables[variableInfo.index].name = newVariableName;
                    }
                    if (newVariableDescription.length > 0) {
                      workflow.workflow_variables[variableInfo.index].description =
                        newVariableDescription;
                    }
                    if (newVariableValue.length > 0) {
                      workflow.workflow_variables[variableInfo.index].value = newVariableValue;
                    }

                    handled = true
                  }
                }

                if (!handled) {
                  // try to find one with the same name
                  const found = workflow.workflow_variables.findIndex(
                    (data) => data.name === newVariableName
                  );
                  if (found !== -1) {
                    if (newVariableName.length > 0) {
                      workflow.workflow_variables[found].name = newVariableName;
                    }
                    if (newVariableDescription.length > 0) {
                      workflow.workflow_variables[found].description =
                        newVariableDescription;
                    }
                    if (newVariableValue.length > 0) {
                      workflow.workflow_variables[found].value = newVariableValue;
                    }
                  } else {
                    workflow.workflow_variables.push({
                      name: newVariableName,
                      description: newVariableDescription,
                      value: newVariableValue,
                      id: uuidv4(),
                    });
                  }
                }

                setVariableInfo({})
                setWorkflow(workflow);
                setVariablesModalOpen(false);
                setNewVariableName("");
                setNewVariableDescription("");
                setNewVariableValue("");
              }}
              color="primary"
            >
              Submit
            </Button>
          </DialogActions>
        </FormControl>
      </Dialog>
    )
  }

  const AuthenticationData = (props) => {
    const selectedApp = props.app;

    const [authenticationOption, setAuthenticationOptions] = React.useState({
      app: JSON.parse(JSON.stringify(selectedApp)),
      fields: {},
      label: "",
      usage: [
        {
          workflow_id: workflow.id,
        },
      ],
      id: uuidv4(),
      active: true,
    });

    if (
      selectedApp.authentication === undefined ||
      selectedApp.authentication.parameters === null ||
      selectedApp.authentication.parameters === undefined ||
      selectedApp.authentication.parameters.length === 0
    ) {
      return null
		/*
		(
        <DialogContent style={{ textAlign: "center", marginTop: 50 }}>
          <Typography variant="h4" id="draggable-dialog-title" style={{ cursor: "move", }}>
            {selectedApp.name} does not require authentication
          </Typography>
        </DialogContent>
      );
	  */
    }

    authenticationOption.app.actions = [];

    for (let paramkey in selectedApp.authentication.parameters) {
      if (
        authenticationOption.fields[
        selectedApp.authentication.parameters[paramkey].name
        ] === undefined
      ) {
        authenticationOption.fields[
          selectedApp.authentication.parameters[paramkey].name
        ] = "";
      }
    }

    const handleSubmitCheck = () => {
      if (authenticationOption.label.length === 0) {
        authenticationOption.label = `Auth for ${selectedApp.name}`;
      }

      // Automatically mapping fields that already exist (predefined).
      // Warning if fields are NOT filled
      for (let paramkey in selectedApp.authentication.parameters) {
        if (authenticationOption.fields[selectedApp.authentication.parameters[paramkey].name].length === 0) {

          if (
            selectedApp.authentication.parameters[paramkey].value !== undefined &&
            selectedApp.authentication.parameters[paramkey].value !== null &&
            selectedApp.authentication.parameters[paramkey].value.length > 0
          ) {
            authenticationOption.fields[
              selectedApp.authentication.parameters[paramkey].name
            ] = selectedApp.authentication.parameters[paramkey].value;
          } else {
            if (
              selectedApp.authentication.parameters[paramkey].schema.type === "bool"
            ) {
              authenticationOption.fields[
                selectedApp.authentication.parameters[paramkey].name
              ] = "false";
            } else {
              toast(
                "Field " +
                selectedApp.authentication.parameters[paramkey].name +
                " can't be empty. If you want it empty, put space."
              );
              return;
            }
          }
        }
      }

      selectedAction.authentication_id = authenticationOption.id;
      selectedAction.selectedAuthentication = authenticationOption;

      console.log("auth option 4: ", authenticationOption)

      if (selectedAction.authentication === undefined || selectedAction.authentication === null) {

        selectedAction.authentication = [authenticationOption]
      } else {

        try {
          selectedAction.authentication.push(authenticationOption)
        } catch (e) {
          //console.log("Error: ", e)
        }
      }

      setSelectedAction(selectedAction)

      var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
      var newFields = [];

	  var warningsent = false
      for (let authkey in newAuthOption.fields) {
        var value = newAuthOption.fields[authkey];

		if (value?.toLowerCase().includes("secret. replace")) {
			value = ""

			if (authkey === "url") {
				// Use default value of the url
				const urlparam = selectedApp.authentication.parameters.find((data) => data.name === "url")
				if (urlparam !== undefined && urlparam !== null) {
					if (urlparam.example !== undefined && urlparam.example !== null && urlparam.example.length > 0) {
						value = urlparam.example
					}
				}
			} else {
				if (!warningsent) {
					warningsent = true
					toast("Warning: As you didn't fill in all fields, be aware that the authentication may fail.")
				}
			}
		}

        newFields.push({
          "key": authkey,
          "value": value,
        });
      }

      newAuthOption.fields = newFields
      setNewAppAuth(newAuthOption)

      if (configureWorkflowModalOpen) {
        //setSelectedAction({})
      }

      setUpdate(authenticationOption.id)
    }

    if (authenticationOption.label === null || authenticationOption.label === undefined) {
      authenticationOption.label = selectedApp.name + " authentication";
    }

    return (
      <div>
        <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
          <div style={{ color: theme.palette.text.primary }}>
            Authentication for {selectedApp.name.replaceAll("_", " ", -1)}
          </div>
        </DialogTitle>
        <DialogContent>
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://shuffler.io/docs/apps#authentication"
            style={{ textDecoration: "none", color: theme.palette.linkColor }}
          >
            What is app authentication?
          </a>
          <div />
          These are required fields for authenticating with {selectedApp.name}
          <div style={{ marginTop: 15 }} />
          <b>Label for you to remember</b>
          <TextField
            style={{
              backgroundColor: theme.palette.inputColor,
              borderRadius: theme.palette?.borderRadius,
            }}
            InputProps={{
              style: {
              },
            }}
            fullWidth
            color="primary"
            placeholder={"Auth july 2020"}
            defaultValue={`Auth for ${selectedApp.name}`}
            onChange={(event) => {
              authenticationOption.label = event.target.value;
            }}
          />
          <Divider
            style={{
              marginTop: 15,
              marginBottom: 15,
              backgroundColor: "rgb(91, 96, 100)",
            }}
          />
          <div />
          {selectedApp.authentication.parameters.map((data, index) => {
            // FIXME: Look for relevant fields in the action that may already be filled in with the same name
            if (data.value === "" || data.value === null || data.value === undefined || data.name === "url") {
              if (selectedAction !== undefined && selectedAction !== null && selectedAction.parameters !== undefined && selectedAction.parameters !== null) {
                for (var fieldkey in selectedAction.parameters) {
                  const field = selectedAction.parameters[fieldkey]
                  if (field.name !== data.name) {
                    continue
                  }

                  if (field.value !== undefined && field.value !== null && field.value.length > 0) {
                    data.value = field.value
                    data.autocomplete = true
                    break
                  }
                }
              }
            }


            return (
              <div key={index} style={{ marginTop: 10 }}>
                <div style={{ display: "flex", }}>
                  <LockOpenIcon style={{
                    marginRight: 10
                  }} />
                  <Typography variant="body1">
                    {data?.name?.endsWith("_basic") ? data?.name?.replace("_basic", "") : data?.name}
                  </Typography>
                </div>

                {data.schema !== undefined &&
                  data.schema !== null &&
                  data.schema.type === "bool" ? (
                  <Select
                    MenuProps={{
                      disableScrollLock: true,
                    }}
                    SelectDisplayProps={{
                      style: {
                      },
                    }}
                    defaultValue={"false"}
                    fullWidth
                    onChange={(e) => {
                      console.log("Value: ", e.target.value);
                      authenticationOption.fields[data.name] = e.target.value;
                    }}
                    style={{
                      backgroundColor: theme.palette.surfaceColor,
                      color: theme.palette.text.primary,
                      height: 50,
                    }}
                  >
                    <MenuItem
                      key={"false"}
                      style={{
                        backgroundColor: theme.palette.inputColor,
                        color: theme.palette.text.primary,
                      }}
                      value={"false"}
                    >
                      false
                    </MenuItem>
                    <MenuItem
                      key={"true"}
                      style={{
                        backgroundColor: theme.palette.inputColor,
                        color: theme.palette.text.primary,
                      }}
                      value={"true"}
                    >
                      true
                    </MenuItem>
                  </Select>
                ) : (
                  <TextField
                    style={{
                      backgroundColor: theme.palette.inputColor,
                      borderRadius: theme.palette?.borderRadius,
                    }}
                    InputProps={{
                      style: {
                      },
                    }}
                    fullWidth
		    multiline={!!data.multiline}
                    type={
                      data.example !== undefined && data.example.includes("**")
                        ? "password"
                        : "text"
                    }
                    color="primary"
                    defaultValue={
                      data.value !== undefined && data.value !== null && !data.value.includes("Secret. Replace") ? data.value : 
					  data?.example !== undefined && data?.example !== null && data?.example !== "" && !data?.example.includes("*") ? data.example : ""
                    }
                    placeholder={data.example}
                    onChange={(event) => {
                      authenticationOption.fields[data.name] = event.target.value;
                    }}
                    id={`${data.name}_auth`}
                  />
                )}
              </div>
            );
          })}
        </DialogContent>
        <DialogActions>
          <Button
            style={{ width: 150, margin: "auto", }}
            disabled={false}
            variant="outlined"
            onClick={() => {
              setAuthenticationOptions(authenticationOption)
              handleSubmitCheck()
            }}
            color="primary"
          >
            Submit
          </Button>
        </DialogActions>
      </div>
    );
  };

  // FIXME: Re-enable?
  const configureWorkflowModal = true ? null :
    configureWorkflowModalOpen && apps.length !== 0 ? (
      <Dialog
        open={configureWorkflowModalOpen}
        PaperProps={{
          style: {
            color: theme.palette.text.primary,
            minWidth: 650,
            border: theme.palette.defaultBorder,

            borderRadius: theme.palette.borderRadius,
            backgroundColor: "black",
          },
        }}
      >
        <IconButton
          style={{
            zIndex: 5000,
            position: "absolute",
            top: 14,
            right: 14,
            color: theme.palette.text.primary,
          }}
          onClick={() => {
            setConfigureWorkflowModalOpen(false);
          }}
        >
          <CloseIcon />
        </IconButton>
        <div style={{ height: 75, width: "100%", background: `linear-gradient(to right, #f86a3e, #fc3922)`, position: "relative", }} />
        <div style={{ padding: "50px 0px 50px 0px", }}>
          <ConfigureWorkflow
            workflow={workflow}
            userdata={userdata}
            globalUrl={globalUrl}
            apps={apps}
            setAuthenticationType={setAuthenticationType}
            setSelectedAction={setSelectedAction}
            setSelectedApp={setSelectedApp}
            setAuthenticationModalOpen={setAuthenticationModalOpen}
            appAuthentication={appAuthentication}
            selectedAction={selectedAction}
            setConfigureWorkflowModalOpen={setConfigureWorkflowModalOpen}
            saveWorkflow={saveWorkflow}
            newWebhook={newWebhook}
            submitSchedule={submitSchedule}
            referenceUrl={referenceUrl}
            workflowExecutions={workflowExecutions}
            getWorkflowExecution={getWorkflowExecution}
            isCloud={isCloud}
          />
        </div>
      </Dialog>
    ) : null;

  // This whole part is redundant. Made it part of Arguments instead?
  const foundIntegrationApp = selectedAction.app_id === "integration" || selectedAction.app_id === "shuffle_agent" ? selectedAction?.parameters?.find(param => param.name === "app_name") : undefined
  const authApp = !authenticationModalOpen ? undefined : foundIntegrationApp === undefined || (selectedApp.id !== "integration" && selectedApp.id !== "shuffle_agent") ? selectedApp : apps.find(app => app.name === foundIntegrationApp?.value) || selectedApp;

  const authenticationModal = authenticationModalOpen ? (
    <Dialog
      PaperComponent={PaperComponent}
      aria-labelledby="draggable-dialog-title"
      hideBackdrop={true}
      disableEnforceFocus={true}
      disableBackdropClick={true}
      style={{ 
        pointerEvents: "none",
     }}
      open={authenticationModalOpen}
      onClose={() => {
        setSelectedMeta(undefined)
      }}
      PaperProps={{
        sx: {
          pointerEvents: "auto",
          color: theme.palette.DialogStyle.color,
          minWidth: "1100px",
          minHeight: "700px",
          maxHeight: "700px",
          overflow: "hidden",
          zIndex: 10012,
          border: theme.palette.DialogStyle.border,

          borderRadius: theme.palette.DialogStyle.borderRadius,
          backgroundColor: theme.palette.DialogStyle.backgroundColor,
          '& .MuiDialogContent-root': {
            backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
          },
          '& .MuiDialogTitle-root': {
            backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
          },
          '& .MuiDialogActions-root': {
            backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
          },
        },
      }}
    >
      <div
        style={{
          zIndex: 5000,
          position: "absolute",
          top: 20,
          right: 75,
          height: 50,
          width: 50,
          backgroundColor: theme.palette.DialogStyle.backgroundColor,
        }}
      >
        { authApp.reference_info === undefined ||
          authApp.reference_info === null ||
          authApp.reference_info.github_url === undefined ||
          authApp.reference_info.github_url === null ||
          authApp.reference_info.github_url.length === 0 ? (

          <a
            rel="noopener noreferrer"
            target="_blank"
            href={"https://github.com/shuffle/python-apps"}
            style={{ textDecoration: "none", color: theme.palette.linkColor }}
          >
            <img
              alt={`Documentation image for ${authApp.name}`}
              src={authApp.large_image}
              style={{
                width: 30,
                height: 30,
                border: "2px solid rgba(255,255,255,0.6)",
                borderRadius: theme.palette?.borderRadius,
                maxHeight: 30,
                maxWidth: 30,
                overflow: "hidden",
                fontSize: 8,
              }}
            />
          </a>
        ) : (
          <a
            rel="noopener noreferrer"
            target="_blank"
            href={authApp.reference_info.github_url}
            style={{ textDecoration: "none", color: theme.palette.linkColor }}
          >
            <img
              alt={`Documentation image for ${authApp.name}`}
              src={authApp.large_image}
              style={{
                width: 30,
                height: 30,
                border: "2px solid rgba(255,255,255,0.6)",
                borderRadius: theme.palette?.borderRadius,
                maxWidth: 30,
                maxHeight: 30,
                overflow: "hidden",
                fontSize: 8,
              }}
            />
          </a>
        )}
      </div>
      <Tooltip
        color="primary"
        title={`Move window`}
        placement="left"
      >
        <IconButton
          id="draggable-dialog-title"
          style={{
            zIndex: 5000,
            position: "absolute",
            top: 14,
            right: 50,
            color: "grey",

            cursor: "move",
          }}
        >
          <DragIndicatorIcon />
        </IconButton>
      </Tooltip>
      <IconButton
        style={{
          zIndex: 5000,
          position: "absolute",
          top: 14,
          right: 18,
          color: "grey",
        }}
        onClick={() => {
          setAuthenticationModalOpen(false);
          if (configureWorkflowModalOpen) {
            //setSelectedAction({});
          }
        }}
      >
        <CloseIcon />
      </IconButton>
      <div style={{ display: "flex", flexDirection: "row", backgroundColor: theme.palette.DialogStyle.backgroundColor }}>
        <div
          style={{
            flex: 
			  authApp.authentication === undefined ||
			  authApp.authentication.parameters === null ||
			  authApp.authentication.parameters === undefined ||
			  authApp.authentication.parameters.length === 0 ? 0 : 2,
            paddingLeft: 15,
            paddingTop: 15,
            paddingBottom: 15,
            minHeight: isMobile ? "90%" : 700,
            maxHeight: isMobile ? "90%" : 700,
            overflowY: "auto",
            overflowX: isMobile ? "auto" : "hidden",
            backgroundColor: theme.palette.DialogStyle.backgroundColor,
          }}
        >
          {authenticationType.type === "oauth2" || authenticationType.type === "oauth2-app" ?
            <AuthenticationOauth2
              saveWorkflow={saveWorkflow}
              selectedApp={authApp}
              workflow={workflow}
              selectedAction={selectedAction}
              authenticationType={authenticationType}
              getAppAuthentication={getAppAuthentication}
              appAuthentication={appAuthentication}
              setSelectedAction={setSelectedAction}
              setNewAppAuth={setNewAppAuth}
              setAuthenticationModalOpen={setAuthenticationModalOpen}
              isCloud={isCloud}
            />
            :
            <AuthenticationData app={authApp} />
          }
        </div>
        <div
          style={{
            flex: 3,
            borderLeft: `1px solid ${theme.palette.inputColor}`,
            padding: "70px 30px 30px 30px",
            maxHeight: 630,
            minHeight: 630,
            overflowY: "auto",
            overflowX: "hidden",
            backgroundColor: theme.palette.DialogStyle.backgroundColor,
          }}
          onLoad={() => {
            /*
          if (isCloud && ReactGA !== undefined) {
            toast("Sending GA info")
            // Google analytics info about what app people are looking at 
            ReactGA.event({
              category: "workflow",
              action: `documentation_load`,
              label: selectedApp.name,
            })
    
          }
          */
          }}
        >
          { authApp.documentation === undefined ||
            authApp.documentation === null ||
            authApp.documentation.length === 0 ? (
            <span
              style={{ textAlign: "center" }}
            >
              <div style={{ textAlign: "left", }}>
                <Markdown
                  components={{
                    img: Img,
                    code: CodeHandler,
                    h1: Heading,
                    h2: Heading,
                    h3: Heading,
                    h4: Heading,
                    h5: Heading,
                    h6: Heading,
                    a: OuterLink,
                  }}
                  id="markdown_wrapper"
                  escapeHtml={false}
                  style={{
                    maxWidth: "100%",
                    minWidth: "100%",
                    textAlign: "left",
                  }}
                >
                  {authApp.description}
                </Markdown>
              </div>
              <Divider
                style={{
                  marginTop: 25,
                  marginBottom: 25,
                  backgroundColor: "rgba(255,255,255,0.6)",
                }}
              />

              <div
                style={{
                  backgroundColor: theme.palette.inputColor,
                  padding: 15,
                  borderRadius: theme.palette?.borderRadius,
                  marginBottom: 30,
                }}
              >
                <Typography variant="h6" style={{ marginBottom: 25, }}>
                  There is no Shuffle-specific documentation for this app yet outside of the general description above. Documentation is written for each api, and is a community effort. We hope to see your contribution!
                </Typography>
                <Button
                  variant="contained"
                  color="primary"
                  onClick={() => {
                    toast.success("Opening remote Github documentation link. Thanks for contributing!")

                    setTimeout(() => {
                      window.open(`https://github.com/Shuffle/openapi-apps/new/master/docs?filename=${authApp.name.toLowerCase()}.md`, "_blank")
                    }, 2500)
                  }}
                >
                  <EditIcon /> &nbsp;&nbsp;Create Docs
                </Button>
              </div>

              <Typography variant="body1" style={{ marginTop: 25 }}>
                Want to help the making of, or improve this app?{" "}
                <br />
                <a
                  rel="noopener noreferrer"
                  target="_blank"
                  href="https://discord.gg/B2CBzUm"
                  style={{ textDecoration: "none", color: theme.palette.linkColor }}
                >
                  Join the community on Discord!
                </a>
              </Typography>

              <Typography variant="h6" style={{ marginTop: 50 }}>
                Want to help change this app directly?
              </Typography>
              { authApp.reference_info === undefined ||
                authApp.reference_info === null ||
                authApp.reference_info.github_url === undefined ||
                authApp.reference_info.github_url === null ||
                authApp.reference_info.github_url.length === 0 ? (
                <span>
                  <Typography variant="body1" style={{ marginTop: 25 }}>
                    <a
                      rel="noopener noreferrer"
                      target="_blank"
                      href={"https://github.com/shuffle/python-apps"}
                      style={{ textDecoration: "none", color: theme.palette.linkColor }}
                    >
                      Check it out on Github!
                    </a>
                  </Typography>
                </span>
              ) : (
                <span>
                  <Typography variant="body1" style={{ marginTop: 25 }}>
                    <a
                      rel="noopener noreferrer"
                      target="_blank"
                      href={authApp.reference_info.github_url}
                      style={{ textDecoration: "none", color: "#f86a3e" }}
                    >
                      Check it out on Github!
                    </a>
                  </Typography>
                </span>
              )}
            </span>
          ) : (
            <div>
              {selectedMeta !== undefined && selectedMeta !== null && Object.getOwnPropertyNames(selectedMeta).length > 0 && selectedMeta.name !== undefined && selectedMeta.name !== null ?
                <div
                  style={{
                    backgroundColor: theme.palette.inputColor,
                    padding: 15,
                    borderRadius: theme.palette?.borderRadius,
                    marginBottom: 30,
                    display: "flex",
                  }}
                >
                  <div style={{ flex: 3, display: "flex", vAlign: "center", position: "sticky", top: 50, }}>
                    {isMobile ? null : (
                      <Typography style={{ display: "inline", marginTop: 6 }}>
                        <a
                          rel="noopener noreferrer"
                          target="_blank"
                          href={selectedMeta.link}
                          style={{ textDecoration: "none", color: "#FF8544" }}
                        >
                          <Button style={{ color: theme.palette.text.primary, }} variant="outlined" color="secondary">
                            <EditIcon /> &nbsp;&nbsp;Edit
                          </Button>
                        </a>
                      </Typography>
                    )}
                    {isMobile ? null : (
                      <div
                        style={{
                          height: "100%",
                          width: 1,
                          backgroundColor: theme.palette.text.primary,
                          marginLeft: 50,
                          marginRight: 50,
                        }}
                      />
                    )}
                    <Typography style={{ display: "inline", marginTop: 11, textWrap: "nowrap" }}>
                      {selectedMeta.read_time} minute
                      {selectedMeta.read_time === 1 ? "" : "s"} to read
                    </Typography>
                  </div>
                  <div style={{ flex: 2 }}>
                    {isMobile ||
                      selectedMeta.contributors === undefined ||
                      selectedMeta.contributors === null ? (
                      ""
                    ) : (
                      <div style={{ margin: 10, height: "100%", display: "inline" }}>
                        {selectedMeta.contributors.slice(0, 7).map((data, index) => {
                          return (
                            <a
                              key={index}
                              rel="noopener noreferrer"
                              target="_blank"
                              href={data.url}
                              style={{ textDecoration: "none", color: "#FF8544" }}
                            >
                              <Tooltip title={data.url} placement="bottom">
                                <img
                                  alt={data.url}
                                  src={data.image}
                                  style={{
                                    marginTop: 5,
                                    marginRight: 10,
                                    height: 40,
                                    borderRadius: 40,
                                  }}
                                />
                              </Tooltip>
                            </a>
                          );
                        })}
                      </div>
                    )}
                  </div>
                </div>
                : null}

              <Markdown
                components={{
                  img: Img,
                  code: CodeHandler,
                  h1: Heading,
                  h2: Heading,
                  h3: Heading,
                  h4: Heading,
                  h5: Heading,
                  h6: Heading,
                  a: OuterLink,
                }}
                id="markdown_wrapper"
                escapeHtml={false}
                style={{
                  maxWidth: "100%", minWidth: "100%",
                }}
              >
                {authApp.documentation}
              </Markdown>
            </div>
          )}
        </div>
      </div>
    </Dialog>
  ) : null;

  const tenzirConfigModal = !tenzirConfigModalOpen ? null :
    <Dialog
      PaperComponent={PaperComponent}
      hideBackdrop={true}
      disableEnforceFocus={true}
      disableBackdropClick={true}
      style={{ pointerEvents: "none" }}
      open={tenzirConfigModalOpen}
      PaperProps={{
        style: {
          pointerEvents: "auto",
          color: theme.palette.text.primary,
          minWidth: 600,
          minHeight: 250,
          maxHeight: 250,
          padding: 50,
          overflow: "hidden",
          zIndex: 10012,
          border: theme.palette.defaultBorder,

          borderRadius: theme.palette.borderRadius,
          backgroundColor: "black",
        },
      }}
    >
      <DialogTitle id="tenzir-config-modal" style={{ cursor: "move" }}>
        <div style={{ color: theme.palette.text.primary }}>Run a Tenzir Pipeline</div>
        <Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
          Runs a Tenzir pipeline. You can use the output of the pipeline in your workflow.
        </Typography>
      </DialogTitle>
      <DialogContent>
        <div>
          <b>Pipeline</b>
          <TextField
            id="topic"
            style={{
              backgroundColor: theme.palette.inputColor,
              borderRadius: theme.palette?.borderRadius,
            }}
            InputProps={{
              style: {},
            }}
            fullWidth
            color="primary"
            placeholder={""}
            defaultValue={selectedOption}
          />
        </div>

      </DialogContent>

      <DialogActions>
        <Button
          style={{ borderRadius: "0px" }}
          variant="contained"
          onClick={() => {
            handleSubmit(selectedTrigger);
          }}
          color="primary"
        >
          Submit
        </Button>
      </DialogActions>
    </Dialog>

  const SuggestionBoxUi = () => {
    const [suggestionValue, setSuggestionValue] = useState("");
    const [suggestionLoading, setSuggestionLoading] = useState(false);
    const [responseMsg, setResponseMsg] = useState("");

    if (suggestionBox === undefined || suggestionBox.open === false) {
      return <span />
    }

    return (
      <div style={{ width: 350, padding: 15, position: "fixed", top: suggestionBox.position.top, left: suggestionBox.position.left, borderRadius: theme.palette?.borderRadius, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.3)", }}>
        {/*
				<AutoFixHighIcon style={{height: 12, width: 12, color: theme.palette.text.primary, position: "absolute", top: 10, right: 24, }} />
				*/}
        <Tooltip
          title="Close"
          placement="top"
          style={{ zIndex: 10011 }}
        >
          <IconButton
            style={{ zIndex: 5000, position: "absolute", top: 0, right: 0 }}
            onClick={(e) => {
              e.preventDefault();
              setSuggestionBox({
                "position": {
                  "top": 500,
                  "left": 500,
                },
                "open": false,
                "value": "",
                "loading": false,
              });
            }}
          >
            <CloseIcon style={{ color: theme.palette.text.primary, height: 12, width: 12, }} />
          </IconButton>
        </Tooltip>
        <form onSubmit={(e, value) => {
          e.preventDefault();
          aiSubmit(suggestionValue, setResponseMsg, setSuggestionLoading)
        }}>
          <TextField
            id="suggestion-textfield"
            style={{ width: "90%" }}
            disabled={suggestionLoading}
            label="What action do you want to add?"
            onChange={(e) => {
              setSuggestionValue(e.target.value)
            }}
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  <Tooltip title="Run search" placement="top">
                    <SendIcon style={{ cursor: "pointer" }} onClick={(e) => {
                      e.preventDefault();
                      aiSubmit(suggestionValue, setResponseMsg, setSuggestionLoading)
                    }} />
                  </Tooltip>
                </InputAdornment>
              ),
            }}
          />
        </form>
        {suggestionLoading === true ?
          <CircularProgress style={{ height: 15, width: 15, marginTop: 5, }} />
          : null}
        {responseMsg.length > 0 ?
          <Typography variant="body2">
            {responseMsg}
          </Typography>
          : null}
      </div>
    )
  }


  /*else if (selectedRevision === undefined || selectedRevision === null || selectedRevision == {} && originalWorkflow !== undefined && originalWorkflow !== null && originalWorkflow !== {}) {
    console.log("Setting original workflow as selected revision")
    setSelectedRevision(originalWorkflow)
  }*/

  const RevisionBox = (props) => {
    const { revision, showBorder, } = props
    if (revision === undefined || revision === null) {
      return null
    }

    var newrevision = JSON.parse(JSON.stringify(revision))
    // Make unix timestamp into ISO timestamp in the format July 27th, 3:05 AM
    // Format: July 27th, 3:05 AM	
    //console.log("Edited time: ", revision.edited)
    // Convert 1692128391 to valid timestamp
    const validTimestamp = newrevision.edited.toString().length === 10 ? newrevision.edited * 1000 : newrevision.edited
    const translatedDate = new Date(validTimestamp).toLocaleString('en-US', { month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true })

    var workflowStatus = newrevision.status !== undefined && newrevision.status !== null && newrevision.status !== "" ? newrevision.status : "test"
    if (newrevision.name !== undefined && newrevision.name !== null && newrevision.name !== "") {
      if (newrevision.name.toLowerCase().includes("test")) {
        workflowStatus = "test"
      }

      if (newrevision.name.toLowerCase().includes("dev") || newrevision.name.toLowerCase().includes("staging") || newrevision.name.toLowerCase().includes("rollback")) {
        workflowStatus = "dev"
      }

      if (newrevision.name.toLowerCase().includes("prod") || newrevision.name.toLowerCase().includes("main")) {
        workflowStatus = "prod"
      }
    }

    return (
      <Paper
        style={{
          padding: "15px 15px 15px 25px", minHeight: 105, maxHeight: 105, cursor: "pointer", backgroundColor: newrevision.edited === selectedVersion.edited ? "rgba(255,255,255,0.3)" : theme.palette.surfaceColor, border: showBorder === true ? `1px solid ${green}` : "1px solid rgba(255,255,255,0.3)", marginBottom: 10,
        }} onClick={(e) => {
          setRightSideBarOpen(false)
          if (newrevision.edited === selectedVersion.edited) {
            console.log("Same revision! No setting.")
            return
          }

          // Should render if it's not the same as workflow.edited
          console.log("Clicked revision: ", newrevision)
          setLastSaved(false)
          setSelectedVersion(newrevision);
          setWorkflow(newrevision)
          setSelectedAction({});
          setSelectedApp({})

          // Remove all cytoscape triggers first?
          if (cy !== undefined && cy !== null) {
            cy.removeListener("select");
            cy.removeListener("unselect");

            cy.removeListener("add");
            cy.removeListener("remove");

            cy.removeListener("mouseover");
            cy.removeListener("mouseout");

            cy.removeListener("drag");
            cy.removeListener("free");
            cy.removeListener("cxttap");

            setElements([])

            cy.remove('*')
            cy.edges().remove()
            cy.nodes().remove()
          }


          // Remove all cy nodes
          setTimeout(() => {
            //toast("Running setupgraph with new revision. Actions: " + newrevision.actions.length)
            setupGraph(newrevision)
          }, 250)


          // Re-adding cytoscape triggers
          if (cy !== undefined && cy !== null) {
            cy.on("select", "node", (e) => {
              onNodeSelect(e, appAuthentication);
            });
            cy.on("select", "edge", (e) => onEdgeSelect(e));

            cy.on("unselect", (e) => onUnselect(e));

            cy.on("add", "node", (e) => onNodeAdded(e));
            cy.on("add", "edge", (e) => onEdgeAdded(e));
            cy.on("remove", "node", (e) => onNodeRemoved(e));
            cy.on("remove", "edge", (e) => onEdgeRemoved(e));

            cy.on("mouseover", "edge", (e) => onEdgeHover(e));
            cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e));
            cy.on("mouseover", "node", (e) => onNodeHover(e));
            cy.on("mouseout", "node", (e) => onNodeHoverOut(e));

            // Handles dragging
            cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction));
            cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction));

            cy.on("cxttap", "node", (e) => onCtxTap(e));


            if (selectedAction.id !== undefined && selectedAction.id !== null && selectedAction.id !== "") {
              setTimeout(() => {
                const foundaction = cy.$id(selectedAction.id)
                if (foundaction !== undefined && foundaction !== null) {
                  foundaction.select()
                }
              }, 250)
            }
          }



          // Need to run through graph setup with this one
        }}>
        <div style={{ display: "flex", }}>
          <span style={{ flex: 5, }}>
            <Typography variant="body1">
              {translatedDate}
              {/* {newrevision.edited.toString().slice(6,10)} | {newrevision.revision_id.slice(0,5)} */}
            </Typography>
          </span>
          <span style={{ flex: 2, }}>
            <Tooltip title="Workflow status. Change in the Edit Workflow panel" placement="top">
              <Chip
                style={{ marginLeft: 10, padding: 0, }}
                label={workflowStatus}
                variant="outlined"
                color="secondary"
              />
            </Tooltip>
          </span>
        </div>
        {/*revision.edited === originalWorkflow.edited ?
					<Typography variant="body2">
						Current version
					</Typography>
				: null*/}
        <div style={{ display: "flex", }}>
          {revision.actions !== undefined && revision.actions !== null ?
            <Tooltip
              color="primary"
              title="Amount of actions"
              placement="bottom"
            >
              <span
                style={{ color: "#979797", display: "flex" }}
              >
                <AppsIcon
                  style={{
                    color: "#979797",
                    marginTop: "auto",
                    marginBottom: "auto",
                  }}
                />
                <Typography
                  style={{
                    marginLeft: 5,
                    marginTop: "auto",
                    marginBottom: "auto",
                  }}
                >
                  {revision.actions.length}
                </Typography>
              </span>
            </Tooltip>
            : null}
          {revision.triggers !== undefined && revision.triggers !== null ?
            <Tooltip
              color="primary"
              title="Amount of triggers"
              placement="bottom"
            >
              <span
                style={{ marginLeft: 15, color: "#979797", display: "flex" }}
              >
                <RestoreIcon
                  style={{
                    color: "#979797",
                    marginTop: "auto",
                    marginBottom: "auto",
                  }}
                />
                <Typography
                  style={{
                    marginLeft: 5,
                    marginTop: "auto",
                    marginBottom: "auto",
                  }}
                >
                  {revision.triggers.length}
                </Typography>
              </span>
            </Tooltip>
            : null}
        </div>
        {revision.updated_by !== undefined && revision.updated_by !== null && revision.updated_by !== "" ?
          <Typography variant="body2" style={{ marginTop: 2, }}>
            {revision.updated_by}
          </Typography>
          : null}
      </Paper>
    )
  }

  const drawerData = originalWorkflow !== undefined && originalWorkflow !== null ?
    <div style={{ height: "100%", backgroundColor: theme.palette.backgroundColor, }}>
      <Typography variant="h4" style={{ paddingLeft: 25, paddingTop: 25, backgroundColor: theme.palette.backgroundColor, }}>
        Version History
      </Typography>
      <Typography variant="body2" color="textSecondary" style={{ paddingLeft: 25, paddingTop: 5, paddingBottom: 5, }}>
        Versions are stored for every change made, up to once per minute. When restoring a version, the changes will not take effect until you save the workflow.
      </Typography>
      <Divider style={{ marginTop: 25, }} />
      <div style={{ height: "100%", }}>
        {/*
			  <div style={{paddingLeft: "25px", paddingRight: "25px", paddingTop: "10px"}}>
				  <div style={{marginBottom: "20px", }}>
					  <Typography variant="h6" style={{marginTop: 10, marginBottom: 5, }}>
						  Current Version
						</Typography>
						<RevisionBox 
						  revision={selectedVersion}
						/>
					</div>

					<Divider
						style={{
							marginBottom: 15,
							height: 1,
							width: "100%",
							backgroundColor: "rgb(91, 96, 100)",
						  }}
					/>
          		</div>
				*/}


        {allRevisions.length > 0 ?
          <div style={{ overflow: "auto", width: "100%", height: "75%", paddingLeft: "25px", paddingRight: "20px", paddingTop: "10px", paddingBottom: "10px" }}>
            {
              allRevisions.map((revision, index) => {
                /*
                       if(revision.edited === selectedVersion.edited){
                         return null
                       }
               */

                return (
                  <RevisionBox
                    revision={revision}
                    key={index}
                    index={index}

                    showBorder={revision.edited === selectedVersion.edited}
                  />
                )
              })
            }
          </div>

          :
          <div style={{ padding: 5, }}>
            <Typography variant="body2">
              No other revisions found. Save your workflow with changes to create a revision.
            </Typography>
          </div>
        }
      </div>
    </div>
    : null

  const workflowRevisions = !showWorkflowRevisions ? null :
    <div style={{ position: "absolute", }}>
      <Drawer
        anchor={"left"}
        open={showWorkflowRevisions}
        onClose={() => {
          //setShowWorkflowRevisions(false)
        }}
        style={{
          resize: "both",
          overflow: "hidden",
          zIndex: 10005,
        }}
        hideBackdrop={true}
        variant="persistent"
        BackdropProps={{
          style: {
            //backgroundColor: "transparent",
          }
        }}
        PaperProps={{
          style: {
            resize: "both",
            overflow: "hidden",
            minWidth: isMobile ? "100%" : 360,
            maxWidth: isMobile ? "100%" : 360,
            backgroundColor: theme.palette.platformColor,
            color: theme.palette.text.primary,
            fontSize: 18,
            zIndex: 15001,
            borderRight: theme.palette.defaultBorder,

            paddingLeft: leftSideBarOpenByClick ? 280 : 100,
            transition: "padding-left 0.3s",

            borderRadius: theme.palette.borderRadius,
            backgroundColor: "black",
          },
        }}
      >
        {drawerData}
      </Drawer>
      <div style={{ position: "fixed", top: 0, left: 0, width: "100%", zIndex: 15000, backgroundColor: theme.palette.platformColor, display: "flex", height: 70, }}>
        <div style={{ flex: 1, display: "flex", paddingLeft: 30, paddingTop: 20, }}>
          {/*selectedRevision.edited !== undefined && selectedRevision.edited !== null && selectedRevision.edited !== originalWorkflow.edited ?
						<Button
							variant="contained"
							color="primary"
							style={{marginLeft: 50, }}
							onClick={() => {
								console.log("Select and close")
							}}
						>
							Restore Version
						</Button>
					: null*/}
        </div>
        <div style={{ textAlign: "center", color: theme.palette.text.primary, flex: 2, paddingTop: 20, }}>
          <Typography variant="h6">
            {selectedVersion?.name}
          </Typography>
        </div>
        <div style={{ flex: 1, itemAlign: "right", textAlign: "right", paddingRight: 25, paddingTop: 10, }}>
          <Button
            onClick={() => {
              setShowWorkflowRevisions(false)
            }}
            color="secondary"
            variant="outlined"
            style={{ marginTop: 10, }}
          >
            Use Version
            <CloseIcon style={{ marginLeft: 10, }} />
          </Button>
        </div>
      </div>
    </div>

  const changeActionParameterCodeMirror = (event, count, data, actionlist, parametername, selectedAction, setSelectedAction) => {

    // FIXME: This exists ONLY to make sure focus + blur actually changes the field value
    // in fields from ParsedAction.jsx such as rightside_field_2
    const simulateTyping = (inputElement, text) => {
      if (!inputElement) {
        console.error("Target element not found!")
        return;
      }

      //console.log("Simulating typing for: ", text, "in", inputElement)

      inputElement.value = ""
      setTimeout(() => {
        text.split("").forEach((char, index) => {
          setTimeout(() => {
            inputElement.value = text.slice(0, index + 1)

            // Simulate an event object like React's synthetic event
            const event = {
              target: {
                value: inputElement.value,
              },
            };

            // Find the onChange handler, assuming you're calling it from here
            if (typeof inputElement.onchange === 'function') {
              inputElement.onchange(event); // Call onChange with the synthetic event
            }

            // Dispatch the input event for React's internal event system
            const inputEvent = new Event("input", { bubbles: true });
            inputElement.dispatchEvent(inputEvent);

          }, 10)
        })
      }, 50)

      setTimeout(() => {
        inputElement.focus()
      }, 500)
    };

    // Check if event.target.value is an array. If it is, split with comma
    if (parametername !== undefined && parametername !== undefined && parametername?.startsWith("${") && parametername?.endsWith("}")) {
      var paramcheckIndex = selectedAction.parameters.findIndex(param => param.name === parametername)
      if (paramcheckIndex !== -1) {
        // Replace the value in the field
        const toReplace = data.replaceAll("\\\"", "\"").replaceAll("\"", "\\\"");
        selectedAction.parameters[paramcheckIndex].value = toReplace
        setSelectedAction(selectedAction)
        setUpdate(Math.random())

        // Find the fieldname
        const clickedFieldId = "rightside_field_" + count;
        const clickedField = document.getElementById(clickedFieldId)
        if (clickedField !== undefined && clickedField !== null) {
          simulateTyping(clickedField, toReplace)

          /*
          clickedField.value = toReplace
          const newEvent = new Event("input", { bubbles: true })
            Object.defineProperty(event, "target", {
            value: { ...clickedField, value: toReplace },
              writable: false,
            })


          //const newEvent = new Event("input", { bubbles: true })
              clickedField.dispatchEvent(newEvent)
          console.log("Found field: ", clickedField)
          */
        }

        //toast("Replaced field!")

        return
      }
    }

    if (data.startsWith("${") && data.endsWith("}")) {
      console.log("Changing field with variable: ", data)

      // PARAM FIX - Gonna use the ID field, even though it's a hack
      var paramcheck = selectedAction.parameters.find(param => param.name === "body")
      if (paramcheck !== undefined) {
        // Escapes all double quotes
        const toReplace = event.target.value.replaceAll("\\\"", "\"").replaceAll("\"", "\\\"");
        if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
          paramcheck["value_replace"] = [{
            "key": data.name,
            "value": toReplace,
          }]

        } else {
          const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
          if (subparamindex === -1) {
            paramcheck["value_replace"].push({
              "key": data.name,
              "value": toReplace,
            })
          } else {
            paramcheck["value_replace"][subparamindex]["value"] = toReplace
          }
        }

        if (paramcheck["value_replace"] === undefined) {
          selectedAction.parameters[count]["value_replace"] = paramcheck
        } else {
          //selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"]
          selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"]
        }

        setSelectedAction(selectedAction)
        return
      }
    }

    if (event.target.value[event.target.value.length - 1] === "." && actionlist.length > 0) {
      var curstring = ""
      var record = false
      for (let [key, keyval] in Object.entries(selectedAction.parameters[count].value)) {
        const item = selectedAction.parameters[count].value[key]
        if (record) {
          curstring += item
        }

        if (item === "$") {
          record = true
          curstring = ""
        }
      }

      if (curstring.length > 0 && actionlist !== null) {
        // Search back in the action list
        curstring = curstring.split(" ").join("_").toLowerCase()
        var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring)
        if (actionItem !== undefined) {
          console.log("Found item: ", actionItem)

          var jsonvalid = true
          try {
            const tmp = String(JSON.parse(actionItem.example))
            if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) {
              jsonvalid = false
            }
          } catch (e) {
            jsonvalid = false
          }
        }
      }
    }

    if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && count === 0) {
      const parsedvalue = data
      if (parsedvalue.includes("#")) {
        const splitparsed = parsedvalue.split(".#.")
        //console.log("Cant contain #: ", splitparsed)
        if (splitparsed.length > 1) {
          //data.value = splitparsed[0]

          selectedAction.parameters[0].value = splitparsed[0]
          selectedAction.parameters[1].value = splitparsed[1]

          selectedAction.parameters[0].autocompleted = true
          selectedAction.parameters[1].autocompleted = true
          setUpdate(Math.random())
        }
      }
    } else {
      if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > count) {
        selectedAction.parameters[count].autocompleted = false
        selectedAction.parameters[count].value = data
      }
    }

    setSelectedAction(selectedAction)
    //setUpdate(Math.random())
  }

  const handleActionParamChange = (actionId, fieldName, newData) => {
	var updateFail = ""

    if (workflow !== undefined) {
      // Find the action with matching id
      const actionIndex = workflow?.actions.findIndex(action => action.id === actionId);
      if (actionIndex >= 0) {
        // Find the parameter with matching name
        const paramIndex = workflow.actions[actionIndex].parameters.findIndex(param => param.name === fieldName);
        if (paramIndex >= 0) {
          // Update the parameter value
          workflow.actions[actionIndex].parameters[paramIndex].value = newData;
          if(selectedAction !== undefined && selectedAction !== null && selectedAction.id === actionId) {
            selectedAction.parameters[paramIndex].value = newData;
            setSelectedAction(selectedAction)
          }
          // Update workflow state to trigger re-render
          setWorkflow({...workflow});
          setLastSaved(false);
		  setUpdate(Math.random())
        } else {
			updateFail = "Parameter is undefined or null"
		}
	  } else {
		  updateFail = "Trigger is undefined or null"
	  }
	} else {
		updateFail = "Workflow is undefined or null"
	}

	if (updateFail !== "") {
		toast.error(updateFail + " - Failed to update subflow parameter value. Please try again.")
	}
  }
  /*
  var foundusecase = {}
  if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0 && userdata !== undefined && userdata !== null && userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0) {
    for (let priokey in userdata.priorities) {
      const prio = userdata.priorities[priokey]
      if (prio.type !== "usecase") {
        continue
      }

      const descsplit = prio.description.split("&")
      var srcapp = ""
      var dstapp = ""
      if (descsplit.length > 0) {
        srcapp = descsplit[0].toLowerCase().replaceAll(" ", "_")

        if (descsplit.length > 2) {
          dstapp = descsplit[2].toLowerCase().replaceAll(" ", "_")
        }
      }

      if (srcapp.length > 0 && dstapp.length > 0) {
        for (let actionkey in workflow.actions) {
          const curaction = workflow.actions[actionkey]
          const appname = curaction.app_name.toLowerCase().replaceAll(" ", "_")
          if (appname === srcapp || appname === dstapp) {
            foundusecase = prio
            break
          }
        }
      }

      if (foundusecase.name !== undefined && foundusecase.name !== null && foundusecase.name !== "") {
        break
      }
    }
  }

  const templatePopup = foundusecase.name === undefined || foundusecase.name === null || foundusecase.name === "" ? null :
  <Slide direction="down" in={true} mountOnEnter unmountOnExit>
    <div style={{position: "fixed", top: "10%", left: "37%", border: "1px solid rgba(255,255,255,0.3)", backgroundColor: theme.palette.inputColor, borderRadius: theme.palette?.borderRadius, display: "flex", }}>
      <WorkflowTemplatePopup 
        isLoggedIn={isLoggedIn}
        userdata={userdata}
        globalUrl={globalUrl}

        title={foundusecase.name.split("Suggested Usecase: ")[1]}
        description={"Suggested usecase based on usage"}

        srcapp={foundusecase.description.split("&")[0]}
        img1={foundusecase.description.split("&")[1]}
        dstapp={foundusecase.description.split("&")[2]}
        img2={foundusecase.description.split("&")[3]}
      />
    </div>
  </Slide>
  */

  const loadedCheck =
    isLoaded && workflowDone ? (
      <div style={{ overflow: 'hidden' }}>
        {newView}
        <VariablesModal variableInfo={variableInfo} setVariableInfo={setVariableInfo} />
        <ExecutionVariableModal variableInfo={variableInfo} setVariableInfo={setVariableInfo} />
        {aiQueryModal}

        <WorkflowGenerationModal
          open={workflowGenerationModalOpen}
          globalUrl={globalUrl}
          isCloud={isCloud}
          workflow={workflow}
          setWorkflow={setWorkflow}
          saveWorkflow={saveWorkflow}
          setWorkflowGenerationModalOpen={setWorkflowGenerationModalOpen}

          theme={theme}
          isMobile={isMobile}
        />

        {conditionsModal}
        {codePopoutModal}
        {workflowRevisions}
        {authenticationModal}
        {tenzirConfigModal}
        {authgroupModal}
        {executionArgumentModal}
        {configureWorkflowModal}

        <SuggestionBoxUi />

        {codeEditorModalOpen ?
          <ShuffleCodeEditor
            cy={cy}
            workflow={workflow}
            expansionModalOpen={codeEditorModalOpen}
            setExpansionModalOpen={setCodeEditorModalOpen}
            isCloud={isCloud}
            globalUrl={globalUrl}
            workflowExecutions={workflowExecutions}
            getParents={getParents}
            // selectedAction={selectedAction}
            // selectedTrigger={selectedTrigger}
            aiSubmit={aiSubmit}
            toolsAppId={toolsApp.id}
            handleTriggerParamChange={handleTriggerParamChange}
            codedata={editorData.value}
            setcodedata={setcodedata}
            selectedEdge={selectedEdge}
            handleActionParamChange={handleActionParamChange}
            handleConditionFieldChange={handleConditionFieldChange}

            // Not working out of the box
            parameterName={editorData.name}
            fieldCount={editorData.field_number}
            actionlist={editorData.actionlist}
            fieldname={editorData.field_id}
            editorData={editorData}

            changeActionParameterCodeMirror={changeActionParameterCodeMirror}
            activeDialog={activeDialog}
            setActiveDialog={setActiveDialog}
            environment={selectedActionEnvironment}
			userdata={userdata}


            setAiQueryModalOpen={setAiQueryModalOpen}
			isWorkflowEditor={editorData?.name === "workflow yaml"}
          />
          : null}

        {editWorkflowModalOpen === true ?
          <EditWorkflow
            workflow={workflow}
            setWorkflow={setWorkflow}
            modalOpen={editWorkflowModalOpen}
            setModalOpen={setEditWorkflowModalOpen}
            isEditing={true}
            userdata={userdata}
            usecases={usecases}

			scrollTo={"mssp_control"}
          />
          : null}

        {/*selectionOpen === true ?
          <div style={{ borderRadius: theme.palette?.borderRadius, backgroundColor: theme.palette.surfaceColor, zIndex: 12501,position:"fixed", left: 190, bottom: 20,top:70, width: 950, overflowY: "scroll"}}>
            <div>
            <IconButton
              style={{
                zIndex: 12502,
                position: "absolute",
                top: 6,
                right: 6,
                color: theme.palette.text.primary,
              }}
              onClick={() => {
                setSelectionOpen(false)
              }}
            >
              <CloseIcon />
            </IconButton>
            </div>
            <div style={{marginTop: 60, marginLeft: 55}}>
            <AppStats 
              globalUrl={globalUrl}
              workflowId={workflow.id}
              {...props}
            />
            </div>
          </div>
        : null*/}

        <div
          id="redline"
          style={{
            display: "none",
          }}
        />

        {showVideo !== undefined && showVideo.length > 0 ?
          <div style={{ borderRadius: theme.palette?.borderRadius, zIndex: 12501, position: "fixed", left: 40, bottom: 150, width: 300, }}>
            <IconButton
              style={{
                zIndex: 12502,
                position: "absolute",
                top: 6,
                right: 6,
                color: theme.palette.text.primary,
              }}
              onClick={() => {
                setShowVideo("")
              }}
            >
              <CloseIcon />
            </IconButton>
            <iframe
              src={showVideo}
              frameBorder={"false"}
              webkitallowfullscreen={"true"}
              mozallowFullscreen={true}
              allowFullScreen={true}
              style={{
                "top": 0,
                "left": 0,
                "maxWidth": 300,
                "minWidth": 300,
              }}
            />
          </div>
          : null}

        <TextField
          id="copy_element_shuffle"
          value={to_be_copied}
          style={{ display: "none" }}
        />
      </div>
    ) : (
      <div style={{ width: 200, margin: "auto", paddingTop: 300, textAlign: "center", }}>
        <CircularProgress style={{}} />
        <Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
          Loading Workflow & Apps...
        </Typography>
      </div>
    );

  // Awful way of handling scroll
  if (scrollConfig !== undefined && setScrollConfig !== undefined && Object.getOwnPropertyNames(selectedAction).length !== 0) {
    const rightSideActionView = document.getElementById("rightside_actions");
    if (rightSideActionView !== undefined && rightSideActionView !== null) {
      if (scrollConfig.top !== null && scrollConfig.top !== undefined && scrollConfig.top !== 0) {
        setTimeout(() => {
          if (scrollConfig.selected !== undefined && scrollConfig.selected !== null) {
            const selectedField = document.getElementById(scrollConfig.selected)
            if (selectedField !== undefined && selectedField !== null) {
              selectedField.focus()
            }
          }
        }, 5);
      } else {
        if (rightSideActionView.scrollTop !== scrollConfig.top) {
          setScrollConfig({
            top: rightSideActionView.scrollTop,
            left: 0,
            selected: "",
          });
        }
      }
    }
  }

  return (
    <div>
      {/* Removed due to missing react router features
				<Prompt when={!lastSaved} message={unloadText} />
			*/}
      {loadedCheck}
      <SearchModal
      open={searchModalOpen}
      onClose={() => setSearchModalOpen(false)}
      workflow={workflow}
      />
    </div>
  );
};

export default AngularWorkflow;
