// Copyright 2022 The Parca Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/* eslint-disable react-hooks/refs */

import React, {useEffect, useState} from 'react';

import {flip, offset, shift, useFloating, type VirtualElement} from '@floating-ui/react';
import {pointer} from 'd3-selection';

interface GraphTooltipProps {
  children: React.ReactNode;
  contextElement: Element | null;
  frozen?: boolean;
}

function createPositionedVirtualElement(contextElement: Element, x = 0, y = 0): VirtualElement {
  const domRect = contextElement.getBoundingClientRect();
  return {
    getBoundingClientRect: () => ({
      width: 0,
      height: 0,
      top: domRect.y + y,
      left: domRect.x + x,
      right: domRect.x + x,
      bottom: domRect.y + y,
      x: domRect.x + x,
      y: domRect.y + y,
      toJSON: () => ({}),
    }),
  };
}

const GraphTooltip = ({
  children,
  contextElement,
  frozen = false,
}: GraphTooltipProps): React.JSX.Element => {
  'use no memo';
  const [isPositioned, setIsPositioned] = useState(false);

  const {refs, floatingStyles, update} = useFloating({
    placement: 'bottom-start',
    strategy: 'fixed',
    middleware: [
      offset({
        mainAxis: 30,
        crossAxis: 30,
      }),
      flip(),
      shift({
        padding: 20,
      }),
    ],
    whileElementsMounted: undefined,
  });

  // Read the latest `frozen` value from inside the mousemove handler without
  // re-binding the listener every time it toggles.
  const frozenRef = React.useRef(frozen);
  frozenRef.current = frozen;

  useEffect(() => {
    if (contextElement === null) return;

    const onMouseMove: EventListenerOrEventListenerObject = (e: Event) => {
      if (frozenRef.current) return; // Hold position while ⇧ Shift is held.
      const rel = pointer(e);
      const tooltipX = rel[0];
      const tooltipY = rel[1];
      const virtualElement = createPositionedVirtualElement(contextElement, tooltipX, tooltipY);
      refs.setReference(virtualElement);
      setIsPositioned(true);
      update();
    };

    contextElement.addEventListener('mousemove', onMouseMove);
    return () => {
      contextElement.removeEventListener('mousemove', onMouseMove);
    };
  }, [contextElement, update, refs]);

  return (
    <div
      ref={refs.setFloating}
      style={{
        ...floatingStyles,
        visibility: !isPositioned ? 'hidden' : 'visible',
        pointerEvents: frozen ? 'auto' : 'none',
      }}
      className="z-50 w-max"
    >
      {children}
    </div>
  );
};

export default GraphTooltip;
