<script type="ts">
  import GraphNode from "./GraphNode.svelte";
  import GraphEdge from "./GraphEdge.svelte";
  import { createEventDispatcher, onMount, tick } from "svelte";
  import * as d3 from "d3";
  import type { TasksFile, TaskDescriptor } from "./tasks";
  import { createEdges } from "./tasks";
  import { taskStatuses } from "./task-status-cache";
import { grabAssignment } from "./ksp-task-grabber";
import TaskDetailEditor from "./TaskDetailEditor.svelte";

  export let tasks: TasksFile;
  export let nodeDraggingEnabled: boolean = false;
  export let selectionToolEnabled: boolean = false;
  export let showHiddenEdges: boolean = false;
  export let selection: Set<TaskDescriptor> = new Set();
  export let showCenterMarker: boolean = false;
  export let showHidden: boolean = false;

  let hoveredTask: null | TaskDescriptor = null;

  // Svelte automatically fills these with a reference
  let container: HTMLElement;
  let clientHeight: number = window.innerHeight;
  let clientWidth: number = window.innerWidth;
  let svgElement: SVGElement;
  let innerSvgGroup: SVGElement;
  let selectionRectangle: [[number, number], [number, number]] | null = null;
  let dragInProgress: boolean = false;
  let tooltipTextElement: SVGTextElement;

  $: nodes = tasks.tasks;
  $: edges = createEdges(nodes);

  const eventDispatcher = createEventDispatcher();
  function nodeClick(task: TaskDescriptor) {
    function eventHandler(e: CustomEvent<MouseEvent>) {
      if (selectionToolEnabled) {
        selection.clear();
        selection.add(task);
        selection = selection;
      }
      eventDispatcher("selectTask", task);
    }
    return eventHandler;
  }
  function nodeDoubleClick(task: TaskDescriptor) {
    function eventHandler(e: CustomEvent<MouseEvent>) {
      eventDispatcher("openTask", task);
    }
    return eventHandler;
  }
  function nodeHover(task: TaskDescriptor) {
    function eventHandler(hovering: CustomEvent<boolean>) {
      if (hovering.detail) {
        hoveredTask = task;
      } else {
        if (hoveredTask?.id == task.id) hoveredTask = null;
      }
    }
    return eventHandler;
  }

  /**
   * Make the SVG drag&zoomable
   **/
  function setupZoom() {
    function zoomed(e) {
      let svg = d3.select(svgElement).select("g");
      svg.attr("transform", e.transform);
    }
    const zoomer = d3.zoom().scaleExtent([0.1, 2]).clickDistance(10);
    zoomer.on("zoom", zoomed);
    d3.select(container).call(zoomer);
  }

  function groupSelectionHandler(e: MouseEvent) {
    // not enabled?
    if (!selectionToolEnabled) return;

    // not a right button?
    if (e.button != 2) return;

    // setup drag start
    const startPos = d3.pointer(e, innerSvgGroup);

    // prevent default
    e.preventDefault();
    e.stopPropagation();

    function updateSelection() {
      selection.clear();
      tasks.tasks.forEach((t) => {
        if (
          selectionRectangle![0][0] < (t.position ?? [0, 0])[0] &&
          (t.position ?? [0, 0])[0] < selectionRectangle![1][0] &&
          selectionRectangle![0][1] < (t.position ?? [0, 0])[1] &&
          (t.position ?? [0, 0])[1] < selectionRectangle![1][1]
        )
          selection.add(t);
      });
      selection = selection;
    }

    // setup mouse move
    function mouseMove(e: MouseEvent) {
      const np = d3.pointer(e, innerSvgGroup);
      selectionRectangle = [
        [Math.min(np[0], startPos[0]), Math.min(np[1], startPos[1])],
        [Math.max(np[0], startPos[0]), Math.max(np[1], startPos[1])],
      ];
      updateSelection();
    }
    window.addEventListener("mousemove", mouseMove);
    window.addEventListener("mouseup", mouseUp, { once: true });

    // setup mouse down
    function mouseUp(e: MouseEvent) {
      // not a right button?
      if (e.button != 2) return;

      e.preventDefault();
      e.stopPropagation();

      // save selection
      const np = d3.pointer(e, innerSvgGroup);
      selectionRectangle = [
        [Math.min(np[0], startPos[0]), Math.min(np[1], startPos[1])],
        [Math.max(np[0], startPos[0]), Math.max(np[1], startPos[1])],
      ];
      updateSelection();
      selectionRectangle = null;

      // cleanup listeners
      window.removeEventListener("mousemove", mouseMove);
    }
  }

  // dragging
  function dragStart(e: MouseEvent) {
    if (!nodeDraggingEnabled) return;

    // is the left button pressed?
    if (e.button != 0) return;

    dragInProgress = true;
    e.preventDefault();
    e.stopPropagation();

    let startPos = d3.pointer(e, innerSvgGroup);

    function drag(e: MouseEvent) {
      if (!nodeDraggingEnabled) return;

      const currPos = d3.pointer(e, innerSvgGroup);
      const [dx, dy] = [currPos[0] - startPos[0], currPos[1] - startPos[1]];
      for (const [t, _] of selection.entries()) {
        t.position = [
          (t.position ?? [0, 0])[0] + dx,
          (t.position ?? [0, 0])[1] + dy,
        ];
      }
      tasks = tasks;
      startPos = currPos;

      e.preventDefault();
      e.stopPropagation();
    }

    function dragStop(e: MouseEvent) {
      if (!nodeDraggingEnabled) return;

      dragInProgress = false;
      e.preventDefault();
      e.stopPropagation();
      window.removeEventListener("mousemove", drag);
    }

    window.addEventListener("mousemove", drag);
    window.addEventListener("mouseup", dragStop, { once: true });
  }

  export function getCurrentSelection(): Set<TaskDescriptor> {
    return selection;
  }

  onMount(() => {
    setupZoom();
  });


  let tooltipMaxPoints: number | null = null;
  let tooltipCurrPoints: number | null = null;
  $: tooltipTextPos = hoveredTask != null ? [(hoveredTask.position ?? [0,0])[0], (hoveredTask.position ?? [0,0])[1] + 40] : [0,0];
  let tooltipBoxWidth = 0;
  let tooltipBoxHeight = 0;
  async function resizeTooltipBox() {
    await tick();
    if (tooltipTextElement == null) return;
    const bbox = tooltipTextElement.getBBox()
    tooltipBoxWidth = bbox.width + 10 + 10;
    tooltipBoxHeight = bbox.height + 5 + 5;
  }
  $: {
    tooltipMaxPoints = null;
    tooltipCurrPoints = null;
    if (hoveredTask != null) {
      const status = $taskStatuses.get(hoveredTask.id);
      if (status) {
        tooltipMaxPoints = status.maxPoints;
        tooltipCurrPoints = status.points;
      }
      else if (hoveredTask.type == 'open-data') {
        tooltipMaxPoints = hoveredTask.points
      }
      resizeTooltipBox();
    }
  };
</script>

<style>
  div {
    width: 100vw;
    height: 100vh;
    position: fixed;
    top: 0px;
    left: 0px;
  }
  :global(#header) {
    z-index: 20;
  }
  :global(#wrapper) {
    display: flex;
    flex-direction: column;
    height: 100%;
  }
  :global(#page) {
    flex-grow: 1;
  }

  :global(#footer) {
    z-index: 20;
  }

  .selectionRectangle {
    fill: transparent;
    stroke-dasharray: 5, 5;
    stroke: gainsboro;
  }

  .tooltip rect {
    stroke: #ffb3a2;
    stroke-width: 2px;
    fill: #000000bb;
  }

  .tooltip text {
    /* stroke: white;*/
    fill: white;
  }
</style>

<div
  bind:this={container}
  bind:clientHeight
  bind:clientWidth
  on:mousedown={groupSelectionHandler}
  on:contextmenu={(e) => {
    if (selectionToolEnabled) {
      e.preventDefault();
      return false;
    }
  }}>
  <!-- The viewBox must be set to 0,0 origin, otherwise the drag&zoom of the graph breaks -->
  <svg bind:this={svgElement} viewBox="{0},{0},{clientWidth},{clientHeight}">
    <g>
      <!-- The translation assures that [0,0] is just bellow current KSP header in the horizontal center of the page -->
      <g
        bind:this={innerSvgGroup}
        transform="translate({clientWidth / 2}, 210)">
        {#if selectionRectangle != null}
          <rect
            class="selectionRectangle"
            x={selectionRectangle[0][0]}
            y={selectionRectangle[0][1]}
            width={selectionRectangle[1][0] - selectionRectangle[0][0]}
            height={selectionRectangle[1][1] - selectionRectangle[0][1]} />
        {/if}
        {#if showCenterMarker}
          <line
            x1="10000"
            y1="0"
            x2="-10000"
            y2="0"
            stroke="gray"
            stroke-width="1px"
            stroke-dasharray="15,15" />
          <line
            x1="0"
            y1="10000"
            x2="0"
            y2="0"
            stroke="gray"
            stroke-width="1px"
            stroke-dasharray="15,15" />
        {/if}
        {#each edges as edge}
          {#if showHidden || !(edge?.dependee?.hidden || edge?.dependency?.hidden)}
            <GraphEdge {edge} showLabelEdge={showHiddenEdges} />
          {/if}
        {/each}
        {#each nodes as task}
          {#if showHidden || !(task.hidden ?? false) }
            <GraphNode
            {task}
            on:mousedown={dragStart}
            selected={selectionToolEnabled && selection.has(task)}
            on:taskClick
            on:click={nodeClick(task)}
            on:hoveringChange={nodeHover(task)}
            status={$taskStatuses.get(task.id)}
            on:dblclick={nodeDoubleClick(task)} />
          {/if}
        {/each}
        {#if hoveredTask != null && hoveredTask.type == "open-data" }
        <g class="tooltip">
          <rect
            x={tooltipTextPos[0]}
            y={tooltipTextPos[1] - 15}
            width={tooltipBoxWidth}
            height={tooltipBoxHeight}
            rx="3">
          </rect>
          <text
              bind:this={tooltipTextElement}
              x={tooltipTextPos[0] + tooltipBoxWidth / 2}
              y={tooltipTextPos[1] + 4}
              text-anchor="middle"
              alignment-baseline="middle">
              {hoveredTask.type == 'open-data' ? hoveredTask.taskReference : "text"} | {tooltipCurrPoints ?? '?'} bod{ "ů yyy"[tooltipCurrPoints ?? 0] ?? "ů" } z {tooltipMaxPoints ?? '?'}
            </text>
        </g>
      {/if}
      </g>
    </g>
  </svg>
</div>
