import { useEffect, useRef, useState, useCallback } from "react";
import type { Editor } from "@tiptap/core";

interface BlockHandleProps {
  editor: Editor;
  containerEl: HTMLElement | null;
}

const BLOCK_SELECTOR =
  ".tiptap > p, .tiptap > h1, .tiptap > h2, .tiptap > h3, " +
  ".tiptap > ul, .tiptap > ol, .tiptap > blockquote, " +
  ".tiptap > pre, .tiptap > hr, .tiptap > table, .tiptap > [data-type]";

/**
 * Notion-like block handle with two controls:
 * - A "+" button to insert a new block below
 * - A drag grip (⋮⋮) to reorder blocks via drag & drop
 *
 * Hover detection works on the entire container width (including left margin)
 * so the handles are easy to reach.
 */
export function BlockHandle({ editor, containerEl }: BlockHandleProps) {
  const handleRef = useRef<HTMLDivElement>(null);
  const [visible, setVisible] = useState(false);
  const [top, setTop] = useState(0);
  const [left, setLeft] = useState(0);
  const activeBlockRef = useRef<HTMLElement | null>(null);
  const blockPosRef = useRef<number | null>(null);
  const dragImageRef = useRef<HTMLDivElement | null>(null);
  const hideTimer = useRef<ReturnType<typeof setTimeout>>();

  const show = useCallback(() => {
    clearTimeout(hideTimer.current);
    setVisible(true);
  }, []);

  const scheduleHide = useCallback(() => {
    clearTimeout(hideTimer.current);
    hideTimer.current = setTimeout(() => {
      setVisible(false);
      activeBlockRef.current = null;
    }, 200);
  }, []);

  const positionHandle = useCallback(
    (block: HTMLElement) => {
      if (!handleRef.current || !containerEl) return;
      const containerRect = containerEl.getBoundingClientRect();
      const blockRect = block.getBoundingClientRect();

      setTop(blockRect.top - containerRect.top + containerEl.scrollTop);
      setLeft(blockRect.left - containerRect.left - 56);
      show();
      activeBlockRef.current = block;

      const pos = editor.view.posAtDOM(block, 0);
      const resolved = editor.state.doc.resolve(pos);
      blockPosRef.current = resolved.before(1);
    },
    [containerEl, editor, show],
  );

  // Find the nearest block using elementFromPoint (O(1) instead of O(n))
  const findBlockAtPoint = useCallback(
    (clientX: number, clientY: number): HTMLElement | null => {
      if (!containerEl) return null;
      // Sample a few X positions to handle margin hover
      const tiptapEl = containerEl.querySelector<HTMLElement>(".tiptap");
      const centerX = tiptapEl
        ? tiptapEl.getBoundingClientRect().left + 20
        : clientX;
      const el = document.elementFromPoint(centerX, clientY) as HTMLElement | null;
      if (!el) return null;
      return el.closest<HTMLElement>(BLOCK_SELECTOR);
    },
    [containerEl],
  );

  // Listen on the whole container with throttle (~50ms)
  useEffect(() => {
    if (!containerEl) return;

    let throttleId = 0;
    const THROTTLE_MS = 50;

    const handleMouseMove = (e: MouseEvent) => {
      if (throttleId) return;
      throttleId = window.setTimeout(() => { throttleId = 0; }, THROTTLE_MS);

      const target = e.target as HTMLElement;

      // Direct block hover (inside editor content)
      const directBlock = target.closest<HTMLElement>(BLOCK_SELECTOR);
      if (directBlock) {
        positionHandle(directBlock);
        return;
      }

      // Hovering in the left margin area - find nearest block by point
      if (
        handleRef.current?.contains(target) ||
        target === containerEl ||
        target.classList.contains("tiptap")
      ) {
        const block = findBlockAtPoint(e.clientX, e.clientY);
        if (block) {
          positionHandle(block);
        }
      }
    };

    const handleMouseLeave = () => {
      scheduleHide();
    };

    containerEl.addEventListener("mousemove", handleMouseMove);
    containerEl.addEventListener("mouseleave", handleMouseLeave);

    return () => {
      containerEl.removeEventListener("mousemove", handleMouseMove);
      containerEl.removeEventListener("mouseleave", handleMouseLeave);
      clearTimeout(hideTimer.current);
      clearTimeout(throttleId);
    };
  }, [containerEl, positionHandle, findBlockAtPoint, scheduleHide]);

  const handleAdd = () => {
    if (!activeBlockRef.current || !editor) return;

    const pos = editor.view.posAtDOM(activeBlockRef.current, 0);
    const resolved = editor.state.doc.resolve(pos);
    const endOfBlock = resolved.end(1);

    editor
      .chain()
      .focus()
      .insertContentAt(endOfBlock, { type: "paragraph" })
      .setTextSelection(endOfBlock + 1)
      .insertContent("/")
      .run();
  };

  const handleDragStart = (e: React.DragEvent) => {
    if (blockPosRef.current === null || !activeBlockRef.current) return;

    const block = activeBlockRef.current;
    const pos = blockPosRef.current;
    const node = editor.state.doc.nodeAt(pos);
    if (!node) return;

    const clone = block.cloneNode(true) as HTMLDivElement;
    clone.style.cssText =
      "position:fixed;top:-9999px;left:-9999px;width:" +
      block.offsetWidth +
      "px;opacity:0.6;pointer-events:none;background:#1a1a1a;border-radius:6px;padding:4px 8px;";
    document.body.appendChild(clone);
    dragImageRef.current = clone;
    e.dataTransfer.setDragImage(clone, 20, 20);

    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData(
      "application/block-handle",
      JSON.stringify({ from: pos, to: pos + node.nodeSize }),
    );

    block.style.opacity = "0.3";
  };

  const handleDragEnd = () => {
    if (activeBlockRef.current) {
      activeBlockRef.current.style.opacity = "";
    }
    if (dragImageRef.current) {
      dragImageRef.current.remove();
      dragImageRef.current = null;
    }
    setVisible(false);
  };

  // Drop handler on the editor
  useEffect(() => {
    if (!containerEl) return;
    const editorEl = containerEl.querySelector(".tiptap");
    if (!editorEl) return;

    const onDragOver = (e: Event) => {
      const de = e as DragEvent;
      if (!de.dataTransfer?.types.includes("application/block-handle")) return;
      de.preventDefault();
      de.dataTransfer!.dropEffect = "move";
    };

    const onDrop = (e: Event) => {
      const de = e as DragEvent;
      const raw = de.dataTransfer?.getData("application/block-handle");
      if (!raw) return;
      de.preventDefault();

      const { from, to } = JSON.parse(raw);
      const node = editor.state.doc.nodeAt(from);
      if (!node) return;

      const coords = { left: de.clientX, top: de.clientY };
      const dropPos = editor.view.posAtCoords(coords);
      if (!dropPos) return;

      const resolved = editor.state.doc.resolve(dropPos.pos);
      let insertBefore = resolved.before(1);
      const targetNode = editor.state.doc.nodeAt(insertBefore);
      if (targetNode) {
        const domNode = editor.view.nodeDOM(insertBefore) as HTMLElement | null;
        if (domNode) {
          const rect = domNode.getBoundingClientRect();
          if (de.clientY > rect.top + rect.height / 2) {
            insertBefore = insertBefore + targetNode.nodeSize;
          }
        }
      }

      if (insertBefore >= from && insertBefore <= to) return;

      const { tr } = editor.state;
      const slice = editor.state.doc.slice(from, to);

      if (insertBefore > from) {
        tr.delete(from, to);
        const mappedPos = tr.mapping.map(insertBefore);
        tr.insert(mappedPos, slice.content);
      } else {
        tr.insert(insertBefore, slice.content);
        const mappedFrom = tr.mapping.map(from);
        const mappedTo = tr.mapping.map(to);
        tr.delete(mappedFrom, mappedTo);
      }

      editor.view.dispatch(tr);
    };

    editorEl.addEventListener("dragover", onDragOver);
    editorEl.addEventListener("drop", onDrop);

    return () => {
      editorEl.removeEventListener("dragover", onDragOver);
      editorEl.removeEventListener("drop", onDrop);
    };
  }, [containerEl, editor]);

  if (!containerEl) return null;

  return (
    <div
      ref={handleRef}
      className="block-handle"
      style={{
        position: "absolute",
        top,
        left,
        opacity: visible ? 1 : 0,
        pointerEvents: visible ? "auto" : "none",
        transition: "opacity 0.12s",
        zIndex: 10,
      }}
      onMouseEnter={show}
      onMouseLeave={scheduleHide}
    >
      <button
        className="block-handle-btn block-handle-add"
        onClick={handleAdd}
        title="Add block below"
      >
        <svg width="18" height="18" viewBox="0 0 14 14" fill="none">
          <path d="M7 1v12M1 7h12" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
        </svg>
      </button>
      <button
        className="block-handle-btn block-handle-grip"
        draggable
        onDragStart={handleDragStart}
        onDragEnd={handleDragEnd}
        title="Drag to reorder"
      >
        <svg width="14" height="18" viewBox="0 0 10 14" fill="none">
          <circle cx="3" cy="2" r="1.2" fill="currentColor" />
          <circle cx="7" cy="2" r="1.2" fill="currentColor" />
          <circle cx="3" cy="7" r="1.2" fill="currentColor" />
          <circle cx="7" cy="7" r="1.2" fill="currentColor" />
          <circle cx="3" cy="12" r="1.2" fill="currentColor" />
          <circle cx="7" cy="12" r="1.2" fill="currentColor" />
        </svg>
      </button>
    </div>
  );
}
