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

import {
  containMediaLayout,
  type MediaRotationDeg,
} from '../../shared/types/containMediaRect';
import { DEFAULT_SCENE_VIEW_CAMERA, type SceneViewCamera } from '../../shared/types/sceneView';

import styles from './ContainedVideo.module.css';

export type ContainedVideoProps = {
  url: string;
  /** Same 90° steps as scene image previewRotationDeg. */
  rotationDeg?: MediaRotationDeg;
  /** Default contain (map overlays). Cover for editor/graph thumbnails. */
  mode?: 'contain' | 'cover';
  /** Зум/пан как у RotatedImage в mode=contain. */
  viewCamera?: SceneViewCamera | null;
  onContentRectChange?: ((rect: { x: number; y: number; w: number; h: number }) => void) | undefined;
  videoRef?: React.Ref<HTMLVideoElement | null>;
  loop?: boolean;
  muted?: boolean;
  playsInline?: boolean;
  autoPlay?: boolean;
  preload?: React.VideoHTMLAttributes<HTMLVideoElement>['preload'];
  className?: string;
  style?: React.CSSProperties;
  onTimeUpdate?: React.VideoHTMLAttributes<HTMLVideoElement>['onTimeUpdate'];
  onLoadedMetadata?: React.VideoHTMLAttributes<HTMLVideoElement>['onLoadedMetadata'];
  onLoadedData?: React.VideoHTMLAttributes<HTMLVideoElement>['onLoadedData'];
  onError?: React.VideoHTMLAttributes<HTMLVideoElement>['onError'];
  children?: React.ReactNode;
};

function useElementSize<T extends HTMLElement>() {
  const ref = useRef<T | null>(null);
  const [size, setSize] = useState<{ w: number; h: number }>({ w: 0, h: 0 });

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const readLayoutSize = () => {
      setSize({ w: el.clientWidth, h: el.clientHeight });
    };
    const ro = new ResizeObserver(() => {
      readLayoutSize();
    });
    ro.observe(el);
    readLayoutSize();
    return () => ro.disconnect();
  }, []);

  return [ref, size] as const;
}

function assignRef<T>(ref: React.Ref<T> | undefined, value: T): void {
  if (!ref) return;
  if (typeof ref === 'function') {
    ref(value);
    return;
  }
  (ref as React.MutableRefObject<T>).current = value;
}

/**
 * Video laid out like RotatedImage: reports the visible content rect
 * so grid / traps / tokens / effects align with the letterboxed (or cover) frame.
 */
export function ContainedVideo({
  url,
  rotationDeg = 0,
  mode = 'contain',
  viewCamera = null,
  onContentRectChange,
  videoRef,
  loop = false,
  muted = false,
  playsInline = true,
  autoPlay = false,
  preload = 'auto',
  className,
  style,
  onTimeUpdate,
  onLoadedMetadata,
  onLoadedData,
  onError,
  children,
}: ContainedVideoProps) {
  const [hostRef, size] = useElementSize<HTMLDivElement>();
  const [mediaSize, setMediaSize] = useState<{ w: number; h: number } | null>(null);
  const elRef = useRef<HTMLVideoElement | null>(null);

  const cam = viewCamera ?? DEFAULT_SCENE_VIEW_CAMERA;
  const viewScale = mode === 'contain' ? Math.max(1, cam.scale) : 1;
  const viewOx = mode === 'contain' ? cam.ox : 0.5;
  const viewOy = mode === 'contain' ? cam.oy : 0.5;

  const syncMediaSize = (el: HTMLVideoElement) => {
    const w0 = el.videoWidth || 0;
    const h0 = el.videoHeight || 0;
    if (w0 <= 0 || h0 <= 0) return;
    setMediaSize((prev) => (prev && prev.w === w0 && prev.h === h0 ? prev : { w: w0, h: h0 }));
  };

  useLayoutEffect(() => {
    const el = elRef.current;
    if (!el) return;
    if (el.readyState >= 1) syncMediaSize(el);
  }, [url]);

  const layout = useMemo(() => {
    if (!mediaSize) return null;
    return containMediaLayout({
      hostW: size.w,
      hostH: size.h,
      mediaW: mediaSize.w,
      mediaH: mediaSize.h,
      scale: viewScale,
      ox: viewOx,
      oy: viewOy,
      rotationDeg,
      mode,
    });
  }, [mediaSize, mode, rotationDeg, size.h, size.w, viewOx, viewOy, viewScale]);

  const contentRect = layout?.contentRect ?? null;

  useEffect(() => {
    if (!onContentRectChange || !contentRect) return;
    onContentRectChange(contentRect);
  }, [contentRect, onContentRectChange]);

  const leftPx = contentRect ? contentRect.x + contentRect.w / 2 : undefined;
  const topPx = contentRect ? contentRect.y + contentRect.h / 2 : undefined;

  return (
    <div
      ref={hostRef}
      className={[styles.root, className].filter(Boolean).join(' ')}
      style={style}
    >
      <video
        ref={(el) => {
          elRef.current = el;
          assignRef(videoRef, el);
        }}
        className={styles.video}
        src={url}
        loop={loop}
        muted={muted}
        playsInline={playsInline}
        autoPlay={autoPlay}
        preload={preload}
        draggable={false}
        onTimeUpdate={onTimeUpdate}
        onLoadedMetadata={(e) => {
          syncMediaSize(e.currentTarget);
          onLoadedMetadata?.(e);
        }}
        onLoadedData={onLoadedData}
        onError={onError}
        style={{
          width: layout ? layout.elementW : '100%',
          height: layout ? layout.elementH : '100%',
          left: leftPx !== undefined ? `${String(leftPx)}px` : '50%',
          top: topPx !== undefined ? `${String(topPx)}px` : '50%',
          objectFit: mediaSize ? undefined : mode,
          transform: `translate(-50%, -50%) rotate(${String(rotationDeg)}deg)`,
        }}
      >
        {children}
      </video>
    </div>
  );
}
