import * as React from "react";
import * as Mantine from "@mantine/core";
import { GraphvizComponent } from '../graphviz';

import {D3Graph, D3GraphData, D3NodeData, defaultGraphForces} from "../d3graph/d3graph";
import {InfoHoverCardOverlay} from "../info_hover_card";

function esc(str) {
  return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}

function esc2(str) {
  return encodeURIComponent(str);
}

// I know global variables are bad, but a GraphViz URL can only call a global function:
const graphvizMap = new Map();
(window as any).graphvizClicked = function(nodeId, button) {
  const callback = graphvizMap.get(nodeId);
  if (callback) callback(button);
}

export type Renderer = 'd3' | 'graphviz';

interface GraphViewProps<N,L> {
  graphData: D3GraphData<N,L>;
  help: JSX.Element;
  graphvizHelp?: JSX.Element;
  mouseUpHandler: (event, mouse:{x:number, y:number}, node?: D3NodeData<N>) => void;
  defaultRenderer?: Renderer;
  children?: any;
}

export function GraphView<N,L>(props: GraphViewProps<N,L>) {
  const {graphData, help, mouseUpHandler, defaultRenderer} = props;

  const [renderer, setRenderer] = React.useState<Renderer>(defaultRenderer || "d3");

  const id = React.useId();

  if (mouseUpHandler) {
    for (const node of graphData.nodes) {
      graphvizMap.set(id+node.id, (button) => mouseUpHandler({button}, {x:0, y:0}, node));
    }
  }

  const dot1 = `digraph {
    bgcolor="transparent";
    ${graphData.nodes.map(node => `"${esc(node.id)}" [${node.label===""?`shape=circle, width=0.3, height=0.3`:`shape=box`}, label="${esc(node.label)}", fillcolor="${node.color}", style="filled,rounded", ${node.bold?`penwidth=4.0,`:``} URL="javascript:${esc2(`graphvizClicked('${id+node.id}', 2)`)}"]`).join('\n')}
    ${graphData.links.map(link => `"${esc(link.source.id || link.source)}" -> "${esc(link.target.id || link.target)}" [label="${esc(link.label)}", fontcolor="${link.color}", color="${link.color}"${link.bidirectional?`, dir=none`:``}]`).join('\n')}
  }`;

  const dot2 = `digraph {
    bgcolor="transparent";
    node[shape=record];
    ${graphData.nodes.filter(node => !node.id.startsWith("V")).map(node => {
      const outgoing = graphData.links.filter(link => (link.source.id || link.source) === node.id);
      return `"${esc(node.id)}" [label="{ ${["", ...outgoing.map(o => `${o.label}`)].join('|')} } | { ${[esc(node.label), ...outgoing.map(o => (o.target.id || o.target).startsWith("V") ? `${esc(o.target.label)}` : `<L${o.label}>`)].join('|')} }", fillcolor="${node.color}", style="filled,rounded", ${node.bold?`penwidth=4.0,`:``} URL="javascript:${esc2(`graphvizClicked('${id+node.id}', 2)`)}"]`;
    }).join('\n')}

    ${graphData.links.filter(link => !link.target.id.startsWith("V")).map(link => `"${esc(link.source.id || link.source)}":"L${link.label}" -> "${esc(link.target.id || link.target)}" [label="${esc(link.label)}", fontcolor="${link.color}", color="${link.color}"${link.bidirectional?`, dir=none`:``}]`).join('\n')}
  }`;

  // @ts-ignore:
  const graphviz = <Mantine.ScrollArea style={{backgroundColor:"#eee"}}>
      <GraphvizComponent dot={dot2}
      />
    </Mantine.ScrollArea>;

  return <Mantine.Stack>
    <Mantine.Group position="center">
      <Mantine.Tooltip withArrow openDelay={300} label={<Mantine.Text><b>D3</b>: Fast, poor layout<br/><b>Graphviz</b>: Slow, nice layout</Mantine.Text>}>
      <Mantine.SegmentedControl
        data={[
          { label: 'D3', value: 'd3' },
          { label: 'Graphviz', value: 'graphviz' },
        ]}
        value={renderer}
        onChange={setRenderer}
      />
      </Mantine.Tooltip>
      {props.children}
      <Mantine.Button onClick={()=>alert(dot2)}>Get Graphviz dot</Mantine.Button>
    </Mantine.Group>
    {renderer==="d3"?
      <InfoHoverCardOverlay contents={help}>
        <D3Graph graph={graphData} forces={defaultGraphForces} mouseUpHandler={mouseUpHandler} />
      </InfoHoverCardOverlay>
      :
      props.graphvizHelp ?
        <InfoHoverCardOverlay contents={props.graphvizHelp}>{graphviz}</InfoHoverCardOverlay>
        : graphviz
    }
  </Mantine.Stack>;
}
