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

import type { AssetId, MaterialId, MaterialsOverlayLayout, MaterialsZoomTool, NpcsZoomTool } from '../../../shared/types';
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT } from '../../../shared/types';
import { useSceneOverlayView } from '../sceneOverlay/SceneOverlayViewContext';
import { useAssetUrl } from '../useAssetImageUrl';

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

type Corner = 'nw' | 'ne' | 'sw' | 'se';

type MaterialOverlayProps = {
  assetId: AssetId | null;
  layout: MaterialsOverlayLayout;
  materialId?: MaterialId;
  editable?: boolean;
  zoomTool?: MaterialsZoomTool | NpcsZoomTool;
  showClose?: boolean;
  onClose?: () => void;
  closeLabel?: string;
  rotateLabel?: string;
  onLayoutChange?: (layout: MaterialsOverlayLayout) => void;
  onZoomAt?: (nx: number, ny: number) => void;
  /** Без собственного root/dim — внутри `SceneOverlayHost`. */
  embedded?: boolean;
  /** Маркеры легенды (норм. координаты картинки). */
  legendMarkers?: readonly { id: string; number: number; nx: number; ny: number }[];
};

function RotateIcon() {
  return (
    <svg className={styles.frameRotateSvg} viewBox="0 0 24 24" width="16" height="16" aria-hidden>
      <path
        d="M20 12a8 8 0 1 1-2.2-5.4"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.8"
        strokeLinecap="round"
      />
      <path d="M20 4v5h-5" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
    </svg>
  );
}

function nextBaseSize(
  viewW: number,
  viewH: number,
  naturalW: number,
  naturalH: number,
): { w: number; h: number } {
  if (naturalW <= 0 || naturalH <= 0 || viewW <= 0 || viewH <= 0) return { w: 200, h: 120 };
  const maxW = viewW * 0.92;
  const maxH = viewH * 0.88;
  const fit = Math.min(maxW / naturalW, maxH / naturalH);
  return { w: naturalW * fit, h: naturalH * fit };
}

function pointerAngleDeg(centerX: number, centerY: number, clientX: number, clientY: number): number {
  return (Math.atan2(clientY - centerY, clientX - centerX) * 180) / Math.PI;
}

function shortestAngleDelta(fromDeg: number, toDeg: number): number {
  let d = toDeg - fromDeg;
  while (d > 180) d -= 360;
  while (d < -180) d += 360;
  return d;
}

function screenToLocal(
  clientX: number,
  clientY: number,
  centerX: number,
  centerY: number,
  rotationDeg: number,
): { x: number; y: number } {
  const rad = (-rotationDeg * Math.PI) / 180;
  const dx = clientX - centerX;
  const dy = clientY - centerY;
  return {
    x: dx * Math.cos(rad) - dy * Math.sin(rad),
    y: dx * Math.sin(rad) + dy * Math.cos(rad),
  };
}

function localToScreenOffset(localX: number, localY: number, rotationDeg: number): { x: number; y: number } {
  const rad = (rotationDeg * Math.PI) / 180;
  return {
    x: localX * Math.cos(rad) - localY * Math.sin(rad),
    y: localX * Math.sin(rad) + localY * Math.cos(rad),
  };
}

export function MaterialOverlay({
  assetId,
  layout,
  materialId,
  editable = false,
  zoomTool = null,
  showClose = false,
  onClose,
  closeLabel = 'Close',
  rotateLabel = 'Rotate',
  onLayoutChange,
  onZoomAt,
  embedded = false,
  legendMarkers,
}: MaterialOverlayProps) {
  const url = useAssetUrl(assetId);
  const host = useSceneOverlayView();
  const localRootRef = useRef<HTMLDivElement | null>(null);
  const rootRef = embedded && host ? host.rootRef : localRootRef;
  const [natural, setNatural] = useState<{ w: number; h: number }>({ w: 1600, h: 900 });
  const [localView, setLocalView] = useState({ w: 1, h: 1 });
  /** Локальный layout + IPC не чаще 1/frame (лайв, без спама pointermove). */
  const [draftLayout, setDraftLayout] = useState<MaterialsOverlayLayout | null>(null);
  const pendingLayoutRef = useRef<MaterialsOverlayLayout | null>(null);
  const draftRafRef = useRef(0);
  const onLayoutChangeRef = useRef(onLayoutChange);
  onLayoutChangeRef.current = onLayoutChange;
  const view = embedded && host ? host.view : localView;
  const dragRef = useRef<
    | { mode: 'move'; startX: number; startY: number; origin: MaterialsOverlayLayout }
    | {
        mode: 'resize';
        corner: Corner;
        origin: MaterialsOverlayLayout;
        baseW: number;
        baseH: number;
        viewW: number;
        viewH: number;
        centerClientX: number;
        centerClientY: number;
      }
    | {
        mode: 'rotate';
        origin: MaterialsOverlayLayout;
        startPointerAngle: number;
        centerX: number;
        centerY: number;
      }
    | null
  >(null);

  useEffect(() => {
    if (embedded) return;
    const el = localRootRef.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);
        setLocalView((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);
    };
  }, [embedded, url]);

  useEffect(() => {
    if (dragRef.current) return;
    setDraftLayout(null);
    pendingLayoutRef.current = null;
  }, [layout]);

  if (!assetId || !url) return null;

  const interactive = editable || showClose || Boolean(zoomTool);
  const zoomCursor =
    zoomTool === 'zoomIn' ? styles.cursorZoomIn : zoomTool === 'zoomOut' ? styles.cursorZoomOut : '';

  const effectiveLayout = draftLayout ?? layout ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT;
  const rotationDeg = effectiveLayout.rotationDeg ?? 0;
  const base = nextBaseSize(view.w, view.h, natural.w, natural.h);
  const w = base.w * effectiveLayout.scale;
  const h = base.h * effectiveLayout.scale;
  const left = effectiveLayout.cx * view.w - w / 2;
  const top = effectiveLayout.cy * view.h - h / 2;

  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),
    };
  };

  const layoutCenterClient = () => {
    const root = rootRef.current;
    if (!root) return { x: 0, y: 0 };
    const r = root.getBoundingClientRect();
    return {
      x: r.left + effectiveLayout.cx * r.width,
      y: r.top + effectiveLayout.cy * r.height,
    };
  };

  const publishDraft = (next: MaterialsOverlayLayout): void => {
    pendingLayoutRef.current = next;
    if (draftRafRef.current !== 0) return;
    draftRafRef.current = window.requestAnimationFrame(() => {
      draftRafRef.current = 0;
      const pending = pendingLayoutRef.current;
      if (!pending) return;
      setDraftLayout(pending);
      onLayoutChangeRef.current?.(pending);
    });
  };

  const onPointerMove = (e: PointerEvent) => {
    const drag = dragRef.current;
    if (!drag || !onLayoutChange) return;

    if (drag.mode === 'rotate') {
      const angle = pointerAngleDeg(drag.centerX, drag.centerY, e.clientX, e.clientY);
      const delta = shortestAngleDelta(drag.startPointerAngle, angle);
      publishDraft({
        ...drag.origin,
        rotationDeg: drag.origin.rotationDeg + delta,
      });
      return;
    }

    const root = rootRef.current;
    if (!root) return;
    const r = root.getBoundingClientRect();

    if (drag.mode === 'move') {
      const dx = (e.clientX - drag.startX) / Math.max(1, r.width);
      const dy = (e.clientY - drag.startY) / Math.max(1, r.height);
      publishDraft({
        ...drag.origin,
        cx: drag.origin.cx + dx,
        cy: drag.origin.cy + dy,
      });
      return;
    }

    const { baseW, baseH, viewW, viewH, origin, corner, centerClientX, centerClientY } = drag;
    const originW = baseW * origin.scale;
    const originH = baseH * origin.scale;
    const originLeft = -originW / 2;
    const originTop = -originH / 2;
    const originRight = originW / 2;
    const originBottom = originH / 2;

    let anchorX = originLeft;
    let anchorY = originTop;
    if (corner === 'nw') {
      anchorX = originRight;
      anchorY = originBottom;
    } else if (corner === 'ne') {
      anchorX = originLeft;
      anchorY = originBottom;
    } else if (corner === 'sw') {
      anchorX = originRight;
      anchorY = originTop;
    }

    const local = screenToLocal(
      e.clientX,
      e.clientY,
      centerClientX,
      centerClientY,
      origin.rotationDeg ?? 0,
    );
    const newW = Math.max(8, Math.abs(local.x - anchorX));
    const newH = Math.max(8, Math.abs(local.y - anchorY));
    const nextScale = Math.max(newW / Math.max(1, baseW), newH / Math.max(1, baseH));
    const ww = baseW * nextScale;
    const hh = baseH * nextScale;

    let nextLeft = anchorX;
    let nextTop = anchorY;
    if (corner === 'nw') {
      nextLeft = anchorX - ww;
      nextTop = anchorY - hh;
    } else if (corner === 'ne') {
      nextLeft = anchorX;
      nextTop = anchorY - hh;
    } else if (corner === 'sw') {
      nextLeft = anchorX - ww;
      nextTop = anchorY;
    }

    const localCenterX = nextLeft + ww / 2;
    const localCenterY = nextTop + hh / 2;
    const screenOffset = localToScreenOffset(localCenterX, localCenterY, origin.rotationDeg ?? 0);

    publishDraft({
      ...origin,
      cx: origin.cx + screenOffset.x / Math.max(1, viewW),
      cy: origin.cy + screenOffset.y / Math.max(1, viewH),
      scale: nextScale,
    });
  };

  const endDrag = () => {
    dragRef.current = null;
    window.removeEventListener('pointermove', onPointerMove);
    window.removeEventListener('pointerup', endDrag);
    if (draftRafRef.current !== 0) {
      window.cancelAnimationFrame(draftRafRef.current);
      draftRafRef.current = 0;
    }
    const finalLayout = pendingLayoutRef.current;
    if (finalLayout) {
      setDraftLayout(finalLayout);
      onLayoutChangeRef.current?.(finalLayout);
    }
  };

  const startDrag = (e: React.PointerEvent, mode: 'move' | 'resize', corner?: Corner) => {
    if (!editable || !onLayoutChange || zoomTool) return;
    e.preventDefault();
    e.stopPropagation();
    if (mode === 'move') {
      dragRef.current = {
        mode: 'move',
        startX: e.clientX,
        startY: e.clientY,
        origin: { ...effectiveLayout },
      };
    } else {
      const center = layoutCenterClient();
      dragRef.current = {
        mode: 'resize',
        corner: corner ?? 'se',
        origin: { ...effectiveLayout },
        baseW: base.w,
        baseH: base.h,
        viewW: view.w,
        viewH: view.h,
        centerClientX: center.x,
        centerClientY: center.y,
      };
    }
    window.addEventListener('pointermove', onPointerMove);
    window.addEventListener('pointerup', endDrag);
  };

  const startRotate = (e: React.PointerEvent) => {
    if (!editable || !onLayoutChange || zoomTool) return;
    e.preventDefault();
    e.stopPropagation();
    const center = layoutCenterClient();
    dragRef.current = {
      mode: 'rotate',
      origin: { ...effectiveLayout },
      startPointerAngle: pointerAngleDeg(center.x, center.y, e.clientX, e.clientY),
      centerX: center.x,
      centerY: center.y,
    };
    window.addEventListener('pointermove', onPointerMove);
    window.addEventListener('pointerup', endDrag);
  };

  const frame = (
    <div
      className={[styles.frame, editable && !zoomTool ? styles.frameEditable : ''].filter(Boolean).join(' ')}
      data-overlay-kind="material"
      {...(materialId ? { 'data-material-id': materialId } : {})}
      style={{
        left,
        top,
        width: w,
        height: h,
        transform: `rotate(${String(rotationDeg)}deg)`,
        transformOrigin: 'center center',
      }}
      onPointerDown={(e) => {
        if (zoomTool) return;
        startDrag(e, 'move');
      }}
    >
      <img
        className={styles.image}
        src={url}
        alt=""
        draggable={false}
        onLoad={(e) => {
          const img = e.currentTarget;
          setNatural({ w: img.naturalWidth || 1, h: img.naturalHeight || 1 });
        }}
      />
      {(legendMarkers ?? []).map((m) => (
        <div
          key={m.id}
          className={styles.legendMarker}
          style={{
            left: `${String(m.nx * 100)}%`,
            top: `${String(m.ny * 100)}%`,
          }}
        >
          {m.number}
        </div>
      ))}
      {editable && !zoomTool
        ? (['nw', 'ne', 'sw', 'se'] as const).map((corner) => (
            <button
              key={corner}
              type="button"
              className={[styles.handle, styles[`handle_${corner}`]].join(' ')}
              aria-label={corner}
              onPointerDown={(e) => startDrag(e, 'resize', corner)}
            />
          ))
        : null}
      {editable && !zoomTool && onLayoutChange ? (
        <button
          type="button"
          className={styles.frameRotate}
          aria-label={rotateLabel}
          title={rotateLabel}
          onPointerDown={startRotate}
        >
          <RotateIcon />
        </button>
      ) : null}
    </div>
  );

  if (embedded) {
    return frame;
  }

  return (
    <div
      ref={localRootRef}
      className={[styles.root, interactive ? styles.interactive : styles.passive, zoomCursor]
        .filter(Boolean)
        .join(' ')}
      role="dialog"
      aria-modal="true"
      onClick={(e) => {
        if (!zoomTool || !onZoomAt) return;
        e.stopPropagation();
        const { nx, ny } = toNorm(e.clientX, e.clientY);
        onZoomAt(nx, ny);
      }}
    >
      <div className={styles.dim} />
      {frame}
      {showClose ? (
        <button
          type="button"
          className={styles.close}
          onClick={onClose}
          aria-label={closeLabel}
          title={closeLabel}
        >
          ×
        </button>
      ) : null}
    </div>
  );
}
