import React, { MutableRefObject, PointerEvent } from "react";
import { Pin } from "./Pin";
import styled from "styled-components";
import Debug from "debug";
import { Selectable } from "../Props";
import { NoteState, Note } from "./Note";
import { motion, PanInfo, useDragControls } from "framer-motion";
import { BugButtonSubmissionContainer } from "../BugButtonApp";
import { Position } from "./PinBoard";
const debug = Debug("userbugreport:DraggablePin");

/**
 * This is to appease react-dnd.
 *
 * Yeah type strings!
 */
export const DraggablePinType = "DraggablePin";

/**
 * Root layout of our draggable, wraps the 'pin' graphic
 * with a draggable notion.
 */
const Root = styled(motion.div)`
  position: absolute;
  display: flex;
`;

/**
 * Display props for our pin -- controls where it shows
 * the pin head.
 */
export type DraggablePinState = Selectable & {
  /**
   * Each pin has an identifier.
   */
  id: string;
  /**
   * The tip of the pin is the position.
   */
  pinHead: {
    x: number;
    y: number;
  };
  pinStyle?: React.CSSProperties;
  noteStyle?: React.CSSProperties;
  offset?: Position;
};

/**
 * Our draggable pin gives off events.
 */
export interface DraggablePinProps extends DraggablePinState, NoteState {
  /**
   * And a bounding box.
   */
  dragConstraints?: MutableRefObject<HTMLElement>;
  readonly?: boolean;
}

/**
 * Animation control on showing the note.
 *
 * We'll be hiding it while dragging to let you see better.
 */
const variants = {
  showing: { opacity: 1, width: "auto", height: "auto" },
  dragging: { opacity: 0, width: 0, height: 0 },
};

/**
 * A pin -- but with the ability to drag and drop its position.
 */
export const DraggablePin: React.FC<DraggablePinProps> = (
  props: DraggablePinProps
) => {
  debug("render", props);
  const [isDragging, setIsDragging] = React.useState(false);
  const {
    submission,
    updateNote,
    selectNote,
  } = BugButtonSubmissionContainer.useContainer();
  const dragControls = useDragControls();

  const startDrag = (event: PointerEvent) => {
    if (props.readonly) return;
    debug("dragging");
    setIsDragging(true);
    dragControls.start(event, { snapToCursor: false });
  };

  const endDrag = async (
    event: MouseEvent | TouchEvent | globalThis.PointerEvent,
    info: PanInfo
  ) => {
    if (props.readonly) return;
    debug("drag to", event, info);
    // dragging is in absolutes, so relativeize it to offset in the pinboard
    const note = submission.notes.find((n) => n.id === props.id);
    const pinHead = {
      x: note.pinHead.x + info.offset.x,
      y: note.pinHead.y + info.offset.y,
    };
    note.pinHead = pinHead;
    await updateNote(note);
    setIsDragging(false);
  };

  const noteChanged = (noteState: NoteState) => {
    if (props.readonly) return;
    debug("noted", noteState);
    const note = submission.notes.find((n) => n.id === props.id);
    updateNote({
      ...note,
      ...noteState,
    });
  };

  return (
    <Root
      style={{
        x: props.pinHead.x,
        y: props.pinHead.y,
      }}
      drag={!props.readonly}
      dragControls={dragControls}
      dragElastic={0}
      dragMomentum={false}
      dragConstraints={props.dragConstraints}
      onDragEnd={endDrag}
    >
      <Pin
        {...props}
        selected={!props.readonly && (props.selected || isDragging)}
        onClick={() => selectNote(props)}
        onPointerDown={startDrag}
        style={{
          ...props.pinStyle,
          cursor: props.readonly ? "default" : "move",
        }}
      />
      <motion.div
        variants={variants}
        animate={isDragging ? "dragging" : "showing"}
        onPointerMove={(e) => {
          e.stopPropagation();
          e.preventDefault();
        }}
      >
        <Note
          {...props}
          style={props.noteStyle}
          onChanged={noteChanged}
          onClick={() => selectNote(props)}
        />
      </motion.div>
    </Root>
  );
};
