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

import type { MaterialLegend, MaterialLegendItem, MaterialLegendMarker } from '../../shared/types';
import {
  asMaterialLegendItemId,
  asMaterialLegendMarkerId,
  EMPTY_MATERIAL_LEGEND,
  hostPointToLegendImageUv,
  legendImageUvToHostPoint,
  nextLegendNumber,
} from '../../shared/types/materialLegend';
import {
  DEFAULT_SCENE_VIEW_CAMERA,
  sceneViewPanBy,
  sceneViewZoomAt,
  type SceneViewCamera,
} from '../../shared/types/sceneView';
import { RotatedImage } from '../shared/RotatedImage';
import { Button, Input } from '../shared/ui/controls';

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

type Props = {
  legend: MaterialLegend | undefined;
  previewUrl: string | null;
  rotationDeg?: 0 | 90 | 180 | 270;
  /** Крупная карта (окно «Материалы»). */
  largeMap?: boolean;
  onChange: (legend: MaterialLegend) => void;
  onRotate?: () => void;
  rotateLabel?: string;
};

type DragMode =
  | { kind: 'pan'; lastX: number; lastY: number }
  | { kind: 'marker'; id: string }
  | null;

const PERSIST_DEBOUNCE_MS = 180;

function rid(prefix: string): string {
  return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
}

function cloneLegend(legend: MaterialLegend | undefined): MaterialLegend {
  const base = legend ?? EMPTY_MATERIAL_LEGEND;
  return {
    enabled: base.enabled,
    items: base.items.map((it) => ({ ...it })),
    markers: base.markers.map((m) => ({ ...m })),
  };
}

export function MaterialLegendEditor({
  legend,
  previewUrl,
  rotationDeg = 0,
  largeMap = false,
  onChange,
  onRotate,
  rotateLabel = 'Повернуть',
}: Props) {
  const [draft, setDraft] = useState<MaterialLegend>(() => cloneLegend(legend));
  const [activeItemId, setActiveItemId] = useState<string | null>(null);
  const [view, setView] = useState<SceneViewCamera>(DEFAULT_SCENE_VIEW_CAMERA);
  const [contentRect, setContentRect] = useState<{ x: number; y: number; w: number; h: number } | null>(
    null,
  );

  const mapRef = useRef<HTMLDivElement | null>(null);
  const dragRef = useRef<DragMode>(null);
  const skipMapClickRef = useRef(false);
  const spaceDownRef = useRef(false);
  const draftRef = useRef(draft);
  draftRef.current = draft;
  const onChangeRef = useRef(onChange);
  onChangeRef.current = onChange;
  const saveTimerRef = useRef(0);
  const dirtyRef = useRef(false);

  const flushPersist = () => {
    if (saveTimerRef.current) {
      window.clearTimeout(saveTimerRef.current);
      saveTimerRef.current = 0;
    }
    if (!dirtyRef.current) return;
    dirtyRef.current = false;
    onChangeRef.current(draftRef.current);
  };

  const schedulePersist = () => {
    dirtyRef.current = true;
    if (saveTimerRef.current) window.clearTimeout(saveTimerRef.current);
    saveTimerRef.current = window.setTimeout(() => {
      saveTimerRef.current = 0;
      dirtyRef.current = false;
      onChangeRef.current(draftRef.current);
    }, PERSIST_DEBOUNCE_MS);
  };

  const applyDraft = (next: MaterialLegend) => {
    draftRef.current = next;
    setDraft(next);
    schedulePersist();
  };

  useEffect(() => {
    return () => {
      if (saveTimerRef.current) window.clearTimeout(saveTimerRef.current);
      if (dirtyRef.current) onChangeRef.current(draftRef.current);
    };
  }, []);

  useEffect(() => {
    flushPersist();
    setDraft(cloneLegend(legend));
    dirtyRef.current = false;
    setActiveItemId(null);
    setView(DEFAULT_SCENE_VIEW_CAMERA);
    // eslint-disable-next-line react-hooks/exhaustive-deps -- reset only when switching material image
  }, [previewUrl]);

  useEffect(() => {
    if (activeItemId && !draft.items.some((i) => i.id === activeItemId)) {
      setActiveItemId(null);
    }
  }, [activeItemId, draft.items]);

  useEffect(() => {
    if (!largeMap) return;
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.code === 'Space') spaceDownRef.current = true;
    };
    const onKeyUp = (e: KeyboardEvent) => {
      if (e.code === 'Space') spaceDownRef.current = false;
    };
    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
    return () => {
      window.removeEventListener('keydown', onKeyDown);
      window.removeEventListener('keyup', onKeyUp);
    };
  }, [largeMap]);

  useEffect(() => {
    if (!largeMap) return;
    const host = mapRef.current;
    if (!host) return;
    const nativeWheel = (e: WheelEvent) => {
      e.preventDefault();
      const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12;
      const cr = contentRect;
      if (!cr) {
        setView((v) => {
          const nextScale = Math.max(1, Math.min(8, v.scale * factor));
          if (nextScale <= 1.001) return { ...DEFAULT_SCENE_VIEW_CAMERA };
          return { ...v, scale: nextScale };
        });
        return;
      }
      const r = host.getBoundingClientRect();
      setView((v) => {
        const containW = cr.w / Math.max(1e-6, v.scale);
        const containH = cr.h / Math.max(1e-6, v.scale);
        return sceneViewZoomAt(v, {
          hostW: r.width,
          hostH: r.height,
          containW,
          containH,
          hostX: e.clientX - r.left,
          hostY: e.clientY - r.top,
          factor,
        });
      });
    };
    host.addEventListener('wheel', nativeWheel, { passive: false });
    return () => host.removeEventListener('wheel', nativeWheel);
  }, [contentRect, largeMap, previewUrl]);

  const activeItem = useMemo(
    () => (activeItemId ? (draft.items.find((i) => i.id === activeItemId) ?? null) : null),
    [activeItemId, draft.items],
  );

  const setEnabled = (enabled: boolean) => {
    applyDraft({ ...draftRef.current, enabled });
  };

  const updateItems = (items: MaterialLegendItem[]) => {
    applyDraft({ ...draftRef.current, enabled: true, items });
  };

  const updateMarkers = (markers: MaterialLegendMarker[]) => {
    applyDraft({ ...draftRef.current, enabled: true, markers });
  };

  const addItem = () => {
    const cur = draftRef.current;
    const number = nextLegendNumber(cur.items);
    const item: MaterialLegendItem = {
      id: asMaterialLegendItemId(rid('li')),
      number,
      text: '',
    };
    updateItems([...cur.items, item]);
    setActiveItemId(item.id);
  };

  const placeMarker = (nx: number, ny: number, number: number) => {
    updateMarkers([
      ...draftRef.current.markers,
      {
        id: asMaterialLegendMarkerId(rid('lm')),
        number,
        nx,
        ny,
      },
    ]);
  };

  const toNorm = (clientX: number, clientY: number): { x: number; y: number } | null => {
    const el = mapRef.current;
    if (!el) return null;
    const r = el.getBoundingClientRect();
    // UV неповёрнутого изображения — тот же space, что у MaterialOverlay на пульте/презентации.
    if (largeMap && contentRect && contentRect.w > 1 && contentRect.h > 1) {
      return hostPointToLegendImageUv(
        clientX - r.left,
        clientY - r.top,
        contentRect,
        rotationDeg,
      );
    }
    const img = el.querySelector('img');
    if (!img) return null;
    const ir = img.getBoundingClientRect();
    if (ir.width < 1 || ir.height < 1) return null;
    return {
      x: Math.max(0, Math.min(1, (clientX - ir.left) / ir.width)),
      y: Math.max(0, Math.min(1, (clientY - ir.top) / ir.height)),
    };
  };

  const onMapPointerDown = (e: React.PointerEvent) => {
    if (!largeMap) return;
    if (e.button === 1 || (e.button === 0 && spaceDownRef.current)) {
      e.preventDefault();
      e.stopPropagation();
      (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
      dragRef.current = { kind: 'pan', lastX: e.clientX, lastY: e.clientY };
      skipMapClickRef.current = true;
    }
  };

  const onMapPointerMove = (e: React.PointerEvent) => {
    const d = dragRef.current;
    if (!d) return;
    if (d.kind === 'pan') {
      const cr = contentRect;
      if (!cr) return;
      const dx = e.clientX - d.lastX;
      const dy = e.clientY - d.lastY;
      d.lastX = e.clientX;
      d.lastY = e.clientY;
      setView((v) => {
        const containW = cr.w / Math.max(1e-6, v.scale);
        const containH = cr.h / Math.max(1e-6, v.scale);
        return sceneViewPanBy(v, { containW, containH, dx, dy });
      });
      return;
    }
    if (d.kind === 'marker') {
      skipMapClickRef.current = true;
      const p = toNorm(e.clientX, e.clientY);
      if (!p) return;
      const nextMarkers = draftRef.current.markers.map((x) =>
        x.id === d.id ? { ...x, nx: p.x, ny: p.y } : x,
      );
      const next = { ...draftRef.current, enabled: true, markers: nextMarkers };
      draftRef.current = next;
      setDraft(next);
      // persist только на pointerup
    }
  };

  const onMapPointerUp = () => {
    const d = dragRef.current;
    if (d?.kind === 'marker') {
      schedulePersist();
    }
    dragRef.current = null;
  };

  const onMapClick = (e: React.MouseEvent) => {
    if (skipMapClickRef.current) {
      skipMapClickRef.current = false;
      return;
    }
    if (dragRef.current) return;
    if (!draft.enabled || !activeItem) return;
    const p = toNorm(e.clientX, e.clientY);
    if (!p) return;
    placeMarker(p.x, p.y, activeItem.number);
  };

  const renderMarkers = () => {
    if (!draft.enabled) return null;
    return draft.markers.map((m) => {
      const style =
        largeMap && contentRect
          ? (() => {
              const p = legendImageUvToHostPoint(m.nx, m.ny, contentRect, rotationDeg);
              return { left: p.x, top: p.y };
            })()
          : {
              left: `${String(m.nx * 100)}%`,
              top: `${String(m.ny * 100)}%`,
            };
      return (
        <div
          key={m.id}
          className={styles.marker}
          style={style}
          onPointerDown={(e) => {
            if (!draft.enabled) return;
            e.stopPropagation();
            e.preventDefault();
            dragRef.current = { kind: 'marker', id: m.id };
            (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
          }}
          onPointerMove={onMapPointerMove}
          onPointerUp={(e) => {
            e.stopPropagation();
            if (dragRef.current?.kind === 'marker') skipMapClickRef.current = true;
            onMapPointerUp();
          }}
          onClick={(e) => {
            e.stopPropagation();
            skipMapClickRef.current = true;
          }}
          onContextMenu={(e) => {
            e.preventDefault();
            e.stopPropagation();
            updateMarkers(draftRef.current.markers.filter((x) => x.id !== m.id));
          }}
          title="Перетащите, чтобы переместить. ПКМ — удалить"
        >
          {m.number}
        </div>
      );
    });
  };

  const mapBlock = previewUrl ? (
    <div
      ref={mapRef}
      className={[
        styles.map,
        largeMap ? styles.mapLarge : '',
        !draft.enabled ? styles.mapIdle : '',
      ]
        .filter(Boolean)
        .join(' ')}
      onDragOver={(e) => {
        if (!draft.enabled) return;
        e.preventDefault();
      }}
      onDrop={(e) => {
        if (!draft.enabled) return;
        e.preventDefault();
        const num = Number(e.dataTransfer.getData('application/x-legend-number'));
        const p = toNorm(e.clientX, e.clientY);
        if (!p || !Number.isFinite(num) || num < 1) return;
        placeMarker(p.x, p.y, Math.round(num));
      }}
      onClick={onMapClick}
      onPointerDown={onMapPointerDown}
      onPointerMove={onMapPointerMove}
      onPointerUp={onMapPointerUp}
      onPointerCancel={onMapPointerUp}
    >
      {largeMap ? (
        <RotatedImage
          url={previewUrl}
          rotationDeg={rotationDeg}
          mode="contain"
          viewCamera={view}
          onContentRectChange={setContentRect}
        />
      ) : (
        <img className={styles.mapImg} src={previewUrl} alt="" draggable={false} />
      )}
      {renderMarkers()}
      {draft.enabled && !activeItem ? (
        <div className={styles.mapHint}>Выберите строку легенды, чтобы ставить метки</div>
      ) : null}
      {largeMap ? (
        <div className={styles.mapHintZoom}>Колесо — зум · СКМ / Space+ЛКМ — пан</div>
      ) : null}
    </div>
  ) : (
    <div className={[styles.map, largeMap ? styles.mapLarge : '', styles.mapIdle].filter(Boolean).join(' ')}>
      <div className={styles.mapEmpty}>Нет изображения</div>
    </div>
  );

  return (
    <div className={[styles.legendBlock, largeMap ? styles.legendBlockLarge : ''].filter(Boolean).join(' ')}>
      {largeMap ? mapBlock : null}

      <div className={styles.legendHeadRow}>
        {onRotate ? (
          <Button onClick={onRotate}>{rotateLabel}</Button>
        ) : null}
        <label className={styles.row}>
          <input type="checkbox" checked={draft.enabled} onChange={(e) => setEnabled(e.target.checked)} />
          <span>Легенда</span>
        </label>
      </div>

      {draft.enabled ? (
        <>
          <div className={styles.row}>
            <Button onClick={addItem}>Добавить пункт</Button>
            <span className={styles.hint}>
              Активная строка подсвечена — клик по картинке ставит метку; клик по метке — перемещение
            </span>
          </div>
          {draft.items.map((item) => {
            const active = item.id === activeItemId;
            return (
              <div
                key={item.id}
                className={[styles.itemRow, active ? styles.itemRowActive : ''].filter(Boolean).join(' ')}
                draggable
                onClick={() => setActiveItemId(item.id)}
                onDragStart={(e) => {
                  e.dataTransfer.setData('application/x-legend-number', String(item.number));
                  setActiveItemId(item.id);
                }}
              >
                <div className={styles.num}>{item.number}</div>
                <Input
                  value={item.text}
                  onChange={(text) => {
                    updateItems(
                      draftRef.current.items.map((it) => (it.id === item.id ? { ...it, text } : it)),
                    );
                  }}
                  placeholder="Описание…"
                />
                <button
                  type="button"
                  className={styles.itemDrag}
                  title="Удалить"
                  onPointerDown={(e) => e.stopPropagation()}
                  onMouseDown={(e) => e.stopPropagation()}
                  onClick={(e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    const cur = draftRef.current;
                    applyDraft({
                      ...cur,
                      enabled: true,
                      items: cur.items.filter((it) => it.id !== item.id),
                      markers: cur.markers.filter((m) => m.number !== item.number),
                    });
                    if (activeItemId === item.id) setActiveItemId(null);
                  }}
                >
                  ×
                </button>
              </div>
            );
          })}
          {!largeMap ? mapBlock : null}
        </>
      ) : largeMap ? null : (
        mapBlock
      )}
    </div>
  );
}
