import { Dispatch, SetStateAction, createContext } from "react";
import { Connection, EdgeType, NodeType, addEdge } from "@xyflow/react";

type Pos = {x: int, y: int,};

export interface Iface {
  add(type: string, pos: Pos, data: any): string;
  remove(id: string[]): void;
  modify(id: string, data: any): void;

  connect(conn: Connection): void;
  disconnect(conn: Connection[]): void;
};

export const Editor = createContext<Iface|undefined>(undefined);
export default Editor;

type SetNodes = Dispatch<SetStateAction<NodeType[]>>;
type SetEdges = Dispatch<SetStateAction<EdgeType[]>>;
export class Impl implements Iface {
  _setNodes: SetNodes;
  _setEdges: SetEdges;
  _nextId: number = 0;

  constructor(setNodes: SetNodes, setEdges: SetEdges, nextId: number = 0) {
    this._setNodes = setNodes;
    this._setEdges = setEdges;
    this._nextId   = nextId;
  }

  add(type: string, pos: Pos, data: any): string {
    const ret = this._genNextId();
    this._setNodes((nds)=>[...nds, {
      id: ret,
      type: type,
      pos: pos,
      data: data,
    }]);
    return ret;
  }
  remove(ids: string[]): void {
    this._setNodes((nds)=>nds.filter((x)=>!ids.includes(x.id)));
  }
  modify(id: string, data: any): void {
    this._setNodes(
      (nds)=>nds.map((n)=>n.id === id? {...n, data: data}: n)
    );
  }

  connect(c: Connection): void {
    this._setEdges((eds)=>addEdge({
      ...c,
      deletable: true,
    }, eds));
  }
  disconnect(cs: Connection[]): void {
    this._setEdges((eds)=>eds.filter((e)=>!cs.some(c=>matchEdgeAndConn(e, c))));
  }

  _genNextId(): number {
    return "n"+(_nextId++);
  }
};

function matchEdgeAndConn(e: Edge, c: Connection) {
  return (
    (e.source === c.source) &&
      ((e.sourceHandle??null) == (c.sourceHandle??null)) &&
      (e.target == c.target) &&
      ((e.targetHandle??null) == (c.targetHandle??null))
  );
}
