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

import IconButton from '@mui/material/IconButton/IconButton';
import { IconLink } from '@tabler/icons-react';
import {
  Node,
  NodeViewWrapper,
  type ReactNodeViewProps,
  ReactNodeViewRenderer,
} from '@tiptap/react';

import { getMediaAttributes } from '../utils';

const SMALL_MEDIA_THRESHOLD = 80;

const ResizableInlineMediaComponent = ({
  node,
  updateAttributes,
  editor,
  selected,
  getPos,
}: ReactNodeViewProps) => {
  const wrapperRef = useRef<HTMLSpanElement | HTMLAnchorElement>(null);
  const mediaRef = useRef<
    HTMLImageElement | HTMLVideoElement | HTMLIFrameElement
  >(null);
  const editorWidth = editor.view.dom.parentElement?.clientWidth;
  const [isSmall, setIsSmall] = useState(false);
  const [isHovering, setIsHovering] = useState(false);

  const { tag, src, srcdoc, width, textAlign, href, target } = node.attrs;
  const hasLink = !!href;

  useEffect(() => {
    if (!hasLink) setIsHovering(false);
  }, [hasLink]);

  useEffect(() => {
    const wrapper = wrapperRef.current;
    const media = mediaRef.current;
    if (!wrapper || !media) return;

    const resizer = document.createElement('span');
    resizer.classList.add('resizer');
    wrapper.appendChild(resizer);

    let startX = 0;
    let startWidth = 0;

    const onMouseDown = (mouseDownEvent: MouseEvent) => {
      mouseDownEvent.preventDefault();
      mouseDownEvent.stopPropagation();

      const pos = typeof getPos === 'function' ? getPos() : null;
      if (typeof pos === 'number') {
        editor.commands.setNodeSelection(pos);
      }

      startX = mouseDownEvent.clientX;
      startWidth = media.offsetWidth;
      media.style.pointerEvents = 'none';
      document.body.style.userSelect = 'none';
      document.body.style.cursor = 'nwse-resize';
      wrapper.style.maxWidth = '100%';

      const controller = new AbortController();
      const { signal } = controller;

      const onMouseMove = (mouseMoveEvent: MouseEvent) => {
        const rawWidth = startWidth + (mouseMoveEvent.clientX - startX);
        const maxWidth = editorWidth ?? rawWidth;
        const newWidth = Math.max(50, Math.min(rawWidth, maxWidth));
        wrapper.style.width = editorWidth
          ? `${(newWidth / editorWidth) * 100}%`
          : '100%';
      };

      const onMouseUp = () => {
        media.style.pointerEvents = '';
        document.body.style.userSelect = '';
        document.body.style.cursor = '';
        updateAttributes({ width: wrapper.style.width });
        controller.abort();
      };

      document.addEventListener('mousemove', onMouseMove, {
        signal,
        passive: true,
      });

      document.addEventListener('mouseup', onMouseUp, {
        signal,
        passive: true,
      });
    };

    resizer.addEventListener('mousedown', onMouseDown);

    return () => {
      resizer.removeEventListener('mousedown', onMouseDown);
      if (resizer.parentNode === wrapper) {
        wrapper.removeChild(resizer);
      }
      document.body.style.cursor = '';
    };
  }, [updateAttributes, editorWidth, editor, getPos]);

  useEffect(() => {
    const media = mediaRef.current;
    if (!media) return;

    const observer = new ResizeObserver(([entry]) => {
      const { width: w, height: h } = entry.contentRect;
      setIsSmall(w < SMALL_MEDIA_THRESHOLD || h < SMALL_MEDIA_THRESHOLD);
    });

    observer.observe(media);
    return () => observer.disconnect();
  }, []);

  const isIframe = tag === 'iframe';
  const isVideo = tag === 'video';
  const renderSrc = isVideo && src && !src.includes('#') ? `${src}#t=0.1` : src;
  const isEditable = editor.isEditable;

  const showLinkButton = useMemo(() => {
    if (!hasLink) return false;
    if (isEditable) return isSmall ? isHovering : true;
    return !isSmall;
  }, [hasLink, isEditable, isSmall, isHovering]);

  const shouldTrackLinkHover = hasLink && isEditable && isSmall;

  const handleLinkHoverChange = (hovering: boolean) => () => {
    if (!shouldTrackLinkHover) return;
    setIsHovering(hovering);
  };

  const openLink = () => {
    window.open(href, target || '_blank', 'noopener,noreferrer');
  };

  const handleClickNodeView = (e: React.MouseEvent) => {
    if (isEditable || !hasLink) return;
    e.preventDefault();
    openLink();
  };

  const handleClickButtonLink = (e: React.MouseEvent) => {
    e.stopPropagation();
    if (!hasLink) return;
    openLink();
  };

  return (
    <NodeViewWrapper
      data-type="resizable-media"
      ref={wrapperRef}
      as="span"
      onClick={handleClickNodeView}
      onMouseEnter={handleLinkHoverChange(true)}
      onMouseLeave={handleLinkHoverChange(false)}
      style={{
        display: 'inline-block',
        verticalAlign: 'middle',
        width,
        maxWidth: '100%',
        textAlign: textAlign || 'inherit',
        position: 'relative',
        pointerEvents: 'auto',
        cursor: !isEditable && hasLink ? 'pointer' : undefined,
      }}
    >
      {React.createElement(tag, {
        ref: mediaRef,
        src: renderSrc,
        ...(tag === 'iframe' && srcdoc ? { srcDoc: srcdoc } : {}),
        ...(isVideo ? { preload: 'metadata' } : {}),
        controls: tag === 'video',
        allowFullScreen: tag === 'iframe',
        style: {
          maxWidth: '100%',
          verticalAlign: 'middle',
          width: '100%',
          height: 'auto',
          aspectRatio: isIframe ? '16 / 9' : undefined,
          objectFit: 'contain',
          pointerEvents:
            isIframe || (isVideo && !selected) ? 'none' : undefined,
        },
      })}
      {showLinkButton && (
        <IconButton
          variant="secondary"
          onClick={handleClickButtonLink}
          sx={{
            position: 'absolute',
            top: theme => theme.spacing(0.5),
            right: theme => theme.spacing(0.5),
            pointerEvents: 'auto',
          }}
        >
          <IconLink size={16} />
        </IconButton>
      )}
    </NodeViewWrapper>
  );
};

const ResizableInlineMedia = Node.create({
  name: 'resizable-media',
  group: 'block',
  atom: true,
  draggable: false,
  selectable: true,

  addAttributes() {
    return {
      src: { default: null },
      srcdoc: { default: null },
      tag: { default: 'img' },
      width: { default: 'auto' },
      href: { default: null },
      target: { default: null },
      rel: { default: null },
      textAlign: {
        default: 'inherit',
        parseHTML: element => element.style.textAlign || 'inherit',
        renderHTML: attributes => {
          if (!attributes.textAlign || attributes.textAlign === 'inherit') {
            return {};
          }

          return {
            style: `text-align: ${attributes.textAlign};`,
          };
        },
      },
    };
  },

  parseHTML() {
    return [
      {
        tag: 'a[data-type="resizable-media"]',
        priority: 100,
        getAttrs: (dom: HTMLElement) => {
          const attrs = getMediaAttributes(dom);
          if (!attrs) return false;

          const isIframe = attrs.tag === 'iframe';
          const textAlign = dom.style.textAlign || attrs.textAlign || 'inherit';

          return {
            ...attrs,
            textAlign,
            href: dom.getAttribute('href'),
            target: dom.getAttribute('target'),
            rel: dom.getAttribute('rel'),
            width: isIframe && attrs.width === 'auto' ? '100%' : attrs.width,
            controls: attrs.tag === 'video',
            allowFullScreen: attrs.tag === 'iframe',
          };
        },
      },
      {
        tag: 'span[data-type="resizable-media"]',
        priority: 100,
        getAttrs: (dom: HTMLElement) => {
          const attrs = getMediaAttributes(dom);
          if (!attrs) return false;

          const isIframe = attrs.tag === 'iframe';

          return {
            ...attrs,
            width: isIframe && attrs.width === 'auto' ? '100%' : attrs.width,
            controls: attrs.tag === 'video',
            allowFullScreen: attrs.tag === 'iframe',
          };
        },
      },
      {
        tag: 'div[data-type="resizable-media"]',
        priority: 100,
        getAttrs: (dom: HTMLElement) => {
          const attrs = getMediaAttributes(dom);
          if (!attrs) return false;

          const isIframe = attrs.tag === 'iframe';

          return {
            ...attrs,
            width: isIframe && attrs.width === 'auto' ? '100%' : attrs.width,
            controls: attrs.tag === 'video',
            allowFullScreen: attrs.tag === 'iframe',
          };
        },
      },
      {
        tag: 'img',
        priority: 51,
        getAttrs: (dom: HTMLElement) => {
          if (dom.closest('[data-type="resizable-media"]')) return false;
          const img = dom as HTMLImageElement;
          return {
            tag: 'img',
            src: img.getAttribute('src'),
            width: img.style.width || 'auto',
            textAlign: img.style.textAlign || 'inherit',
          };
        },
      },
      {
        tag: 'video',
        priority: 51,
        getAttrs: (dom: HTMLElement) => {
          if (dom.closest('[data-type="resizable-media"]')) return false;
          const video = dom as HTMLVideoElement;
          const src =
            video.getAttribute('src') ||
            video.querySelector('source')?.getAttribute('src') ||
            null;
          return {
            tag: 'video',
            src,
            width: video.style.width || 'auto',
            textAlign: video.style.textAlign || 'inherit',
          };
        },
      },
      {
        tag: 'iframe',
        priority: 51,
        getAttrs: (dom: HTMLElement) => {
          if (dom.closest('[data-type="resizable-media"]')) return false;
          const iframe = dom as HTMLIFrameElement;
          return {
            tag: 'iframe',
            src: iframe.getAttribute('src'),
            srcdoc: iframe.getAttribute('srcdoc'),
            width: iframe.style.width || '100%',
            textAlign: iframe.style.textAlign || 'inherit',
          };
        },
      },
    ];
  },

  renderHTML({ node }) {
    const { tag, width, textAlign, style, href, target, rel, ...attrs } =
      node.attrs;
    const hasLink = !!href;

    const innerContent = [
      'span',
      {
        style: `display: inline-block; width: ${width};`,
      },
      [tag, attrs],
    ];

    const wrapperTag = hasLink ? 'a' : 'span';
    const wrapperStyles = ['display: block', 'max-width: 100%'];
    if (textAlign && textAlign !== 'inherit') {
      wrapperStyles.push(`text-align: ${textAlign}`);
    }
    const wrapperAttrs: Record<string, string> = {
      'data-type': 'resizable-media',
      style: `${wrapperStyles.join('; ')};`,
    };

    if (hasLink) {
      wrapperAttrs.href = href;
      wrapperAttrs.target = target || '_blank';
      wrapperAttrs.rel = rel || 'noopener noreferrer nofollow';
    }

    return [wrapperTag, wrapperAttrs, innerContent];
  },

  addNodeView() {
    return ReactNodeViewRenderer(ResizableInlineMediaComponent, {
      as: 'span',
      attrs: ({ node }) => {
        const { textAlign } = node.attrs;
        return {
          style: `-webkit-user-drag: none; user-drag: none; display: block; pointer-events: none; ${textAlign ? `text-align: ${textAlign};` : ''}`,
        };
      },
    });
  },
});

export default ResizableInlineMedia;
