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

import type { MaterialsZoomTool, NpcsZoomTool } from '../../../shared/types';
import styles from '../materials/MaterialOverlay.module.css';

import { overlayRootStyle, type SceneOverlayViewport } from './overlayViewport';
import { SceneOverlayViewContext } from './SceneOverlayViewContext';

export type SceneOverlayCloseAction = {
  key: string;
  label: string;
  onClose: () => void;
};

type SceneOverlayHostProps = {
  /** Есть ли что показывать (материал и/или NPC). */
  active: boolean;
  /** Область картинки сцены (contain); координаты относительно родителя. */
  viewport?: SceneOverlayViewport | null;
  /** Рамка видимой области (предпросмотр пульта). */
  showViewportGuide?: boolean;
  zoomTool?: MaterialsZoomTool | NpcsZoomTool;
  onZoomAt?: (nx: number, ny: number, target: EventTarget | null) => void;
  closes?: readonly SceneOverlayCloseAction[];
  children: React.ReactNode;
};

/**
 * Общий слой подложки для Materials + NPCs: один root и один `.dim`.
 * Кадры остаются в дочерних оверлеях (`embedded`).
 */
export function SceneOverlayHost({
  active,
  viewport = null,
  showViewportGuide = false,
  zoomTool = null,
  onZoomAt,
  closes = [],
  children,
}: SceneOverlayHostProps) {
  const rootRef = useRef<HTMLDivElement | null>(null);
  const [view, setView] = useState({ w: 1, h: 1 });

  useEffect(() => {
    const el = rootRef.current;
    if (!el) return;
    let raf = 0;
    const sync = () => {
      if (raf !== 0) return;
      raf = window.requestAnimationFrame(() => {
        raf = 0;
        const w = Math.max(1, el.clientWidth);
        const h = Math.max(1, el.clientHeight);
        setView((prev) => (prev.w === w && prev.h === h ? prev : { w, h }));
      });
    };
    sync();
    const ro = new ResizeObserver(sync);
    ro.observe(el);
    return () => {
      ro.disconnect();
      if (raf !== 0) window.cancelAnimationFrame(raf);
    };
  }, [active]);

  const ctx = useMemo(() => ({ rootRef, view }), [view]);

  if (!active && !showViewportGuide) return null;

  const captureZoom = Boolean(zoomTool && onZoomAt);
  const zoomCursor =
    zoomTool === 'zoomIn' ? styles.cursorZoomIn : zoomTool === 'zoomOut' ? styles.cursorZoomOut : '';

  const toNorm = (clientX: number, clientY: number) => {
    const root = rootRef.current;
    if (!root) return { nx: 0.5, ny: 0.5 };
    const r = root.getBoundingClientRect();
    return {
      nx: (clientX - r.left) / Math.max(1, r.width),
      ny: (clientY - r.top) / Math.max(1, r.height),
    };
  };

  return (
    <SceneOverlayViewContext.Provider value={ctx}>
      <div
        ref={rootRef}
        className={[styles.root, styles.hostHitThrough, captureZoom ? styles.captureZoom : '', zoomCursor]
          .filter(Boolean)
          .join(' ')}
        style={overlayRootStyle(viewport)}
        role="presentation"
        onClick={(e) => {
          if (!captureZoom || !onZoomAt) return;
          e.stopPropagation();
          const { nx, ny } = toNorm(e.clientX, e.clientY);
          onZoomAt(nx, ny, e.target);
        }}
      >
        {showViewportGuide ? <div className={styles.viewportGuide} aria-hidden /> : null}
        <div className={styles.dim} />
        {children}
        {closes.length > 0 ? (
          <div className={styles.closeStack}>
            {closes.map((c) => (
              <button
                key={c.key}
                type="button"
                className={styles.close}
                onClick={(e) => {
                  e.stopPropagation();
                  c.onClose();
                }}
                aria-label={c.label}
                title={c.label}
              >
                ×
              </button>
            ))}
          </div>
        ) : null}
      </div>
    </SceneOverlayViewContext.Provider>
  );
}
