import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import type { ReactElement, ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { CloseIcon, DownloadIcon } from '../../../shared/ui';

export interface ViewerItem {
  type: 'image' | 'video';
  url: string;
  name: string;
}

interface ViewerContextValue {
  open: (item: ViewerItem) => void;
}

const ViewerContext = createContext<ViewerContextValue | null>(null);

export const useMediaViewer = (): ViewerContextValue => {
  const context = useContext(ViewerContext);
  if (context === null) {
    throw new Error('useMediaViewer must be used within a MediaViewerProvider');
  }
  return context;
};

interface Point {
  x: number;
  y: number;
}

const MIN_SCALE = 1;
const MAX_SCALE = 4;

const Overlay = ({ item, onClose }: { item: ViewerItem; onClose: () => void }): ReactElement => {
  const [scale, setScale] = useState(1);
  const [offset, setOffset] = useState<Point>({ x: 0, y: 0 });
  const dragging = useRef<{ startX: number; startY: number; baseX: number; baseY: number } | null>(
    null,
  );

  const reset = useCallback((): void => {
    setScale(1);
    setOffset({ x: 0, y: 0 });
  }, []);

  useEffect(() => {
    const onKey = (event: KeyboardEvent): void => {
      if (event.key === 'Escape') {
        onClose();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => {
      window.removeEventListener('keydown', onKey);
    };
  }, [onClose]);

  const onWheel = (event: React.WheelEvent): void => {
    if (item.type !== 'image') {
      return;
    }
    const nextScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale - event.deltaY * 0.0015));
    setScale(nextScale);
    if (nextScale === 1) {
      setOffset({ x: 0, y: 0 });
    }
  };

  const onPointerDown = (event: React.PointerEvent): void => {
    if (scale <= 1) {
      return;
    }
    event.currentTarget.setPointerCapture(event.pointerId);
    dragging.current = {
      startX: event.clientX,
      startY: event.clientY,
      baseX: offset.x,
      baseY: offset.y,
    };
  };

  const onPointerMove = (event: React.PointerEvent): void => {
    const state = dragging.current;
    if (state === null) {
      return;
    }
    setOffset({
      x: state.baseX + (event.clientX - state.startX),
      y: state.baseY + (event.clientY - state.startY),
    });
  };

  const onPointerUp = (): void => {
    dragging.current = null;
  };

  return (
    <div className="viewer" role="dialog" aria-modal="true" onClick={onClose}>
      <div className="viewer-toolbar" onClick={(event) => {
              event.stopPropagation();
            }}>
        <span className="viewer-name">{item.name}</span>
        <div className="viewer-actions">
          <a
            className="icon-btn viewer-btn"
            href={item.url}
            download={item.name}
            aria-label="Download"
            onClick={(event) => {
              event.stopPropagation();
            }}
          >
            <DownloadIcon />
          </a>
          <button type="button" className="icon-btn viewer-btn" aria-label="Close" onClick={onClose}>
            <CloseIcon />
          </button>
        </div>
      </div>

      <div className="viewer-stage" onClick={(event) => {
              event.stopPropagation();
            }}>
        {item.type === 'image' ? (
          <img
            src={item.url}
            alt={item.name}
            className="viewer-img"
            draggable={false}
            style={{
              transform: `translate(${String(offset.x)}px, ${String(offset.y)}px) scale(${String(scale)})`,
              cursor: scale > 1 ? 'grab' : 'zoom-in',
            }}
            onWheel={onWheel}
            onPointerDown={onPointerDown}
            onPointerMove={onPointerMove}
            onPointerUp={onPointerUp}
            onDoubleClick={reset}
            onClick={() => {
              if (scale === 1) {
                setScale(2.2);
              } else {
                reset();
              }
            }}
          />
        ) : (
          <video src={item.url} className="viewer-video" controls autoPlay />
        )}
      </div>
    </div>
  );
};

export const MediaViewerProvider = ({ children }: { children: ReactNode }): ReactElement => {
  const [item, setItem] = useState<ViewerItem | null>(null);

  const open = useCallback((next: ViewerItem): void => {
    setItem(next);
  }, []);
  const close = useCallback((): void => {
    setItem(null);
  }, []);

  const value = useMemo<ViewerContextValue>(() => ({ open }), [open]);

  return (
    <ViewerContext.Provider value={value}>
      {children}
      {item !== null ? createPortal(<Overlay item={item} onClose={close} />, document.body) : null}
    </ViewerContext.Provider>
  );
};
