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

import { ipcChannels, type SessionState } from '../../shared/ipc/contracts';
import type { SceneTrap, SceneTrapType } from '../../shared/types';
import {
  asSceneTrapId,
  DEFAULT_SCENE_TRAP_SIZE_N,
  SCENE_TRAP_TYPES,
  trapTypeLabelRu,
} from '../../shared/types/sceneTraps';
import { sceneViewPanBy, sceneViewZoomAt } from '../../shared/types/sceneView';
import { getDndApi } from '../shared/dndApi';
import { RotatedImage } from '../shared/RotatedImage';
import { TrapGlyph } from '../shared/traps/TrapGlyph';
import { Button } from '../shared/ui/controls';
import { useAssetUrl } from '../shared/useAssetImageUrl';

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

type LocalView = { scale: number; ox: number; oy: number };

type DragMode =
  | { kind: 'pan'; lastX: number; lastY: number }
  | { kind: 'move'; trapId: string; startNx: number; startNy: number; pointerNx: number; pointerNy: number }
  | { kind: 'resize'; trapId: string; startSize: number; startDist: number }
  | null;

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

export function SceneEditorApp() {
  const api = getDndApi();
  const [session, setSession] = useState<SessionState | null>(null);
  const [trapsOpen, setTrapsOpen] = useState(true);
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [view, setView] = useState<LocalView>({ scale: 1, ox: 0.5, oy: 0.5 });
  const [contentRect, setContentRect] = useState<{ x: number; y: number; w: number; h: number } | null>(
    null,
  );
  const hostRef = useRef<HTMLDivElement | null>(null);
  const dragRef = useRef<DragMode>(null);
  const saveTimerRef = useRef(0);
  const spaceDownRef = useRef(false);

  const project = session?.project ?? null;
  const sceneId = project?.currentSceneId ?? null;
  const scene = sceneId && project ? project.scenes[sceneId] : undefined;
  const url = useAssetUrl(scene?.previewAssetId ?? null);
  const rot = scene?.previewRotationDeg ?? 0;
  const [localTraps, setLocalTraps] = useState<SceneTrap[]>([]);
  const trapsRef = useRef<SceneTrap[]>([]);
  trapsRef.current = localTraps;

  useEffect(() => {
    setLocalTraps(scene?.traps ?? []);
    setSelectedId(null);
    setView({ scale: 1, ox: 0.5, oy: 0.5 });
  }, [sceneId, scene?.previewAssetId]);

  useEffect(() => {
    // External updates (other windows) — sync when not dragging
    if (dragRef.current) return;
    setLocalTraps(scene?.traps ?? []);
  }, [scene?.traps]);

  useEffect(() => {
    void api.invoke(ipcChannels.project.get, {}).then(({ project: p }) => {
      setSession({ project: p, currentSceneId: p?.currentSceneId ?? null });
    });
    return api.on(ipcChannels.session.stateChanged, ({ state }) => {
      setSession(state);
    });
  }, [api]);

  const persistTraps = useCallback(
    async (next: SceneTrap[]) => {
      if (!sceneId) return;
      setLocalTraps(next);
      trapsRef.current = next;
      if (saveTimerRef.current) window.clearTimeout(saveTimerRef.current);
      saveTimerRef.current = window.setTimeout(() => {
        void api.invoke(ipcChannels.project.updateScene, { sceneId, patch: { traps: next } });
      }, 120);
    },
    [api, sceneId],
  );

  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.code === 'Space') spaceDownRef.current = true;
      if ((e.key === 'Delete' || e.key === 'Backspace') && selectedId && sceneId) {
        e.preventDefault();
        const next = trapsRef.current.filter((t) => t.id !== selectedId);
        setSelectedId(null);
        void persistTraps(next);
      }
    };
    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);
    };
  }, [persistTraps, sceneId, selectedId]);

  const hostToNorm = (clientX: number, clientY: number): { x: number; y: number } | null => {
    const host = hostRef.current;
    const cr = contentRect;
    if (!host || !cr || cr.w < 1 || cr.h < 1) return null;
    const r = host.getBoundingClientRect();
    return {
      x: Math.max(0, Math.min(1, (clientX - (r.left + cr.x)) / cr.w)),
      y: Math.max(0, Math.min(1, (clientY - (r.top + cr.y)) / cr.h)),
    };
  };

  const onWheel = (_e: React.WheelEvent) => {
    // native non-passive listener handles zoom
  };
  void onWheel;

  const viewCamera = useMemo(() => view, [view]);

  useEffect(() => {
    const host = hostRef.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 { scale: 1, ox: 0.5, oy: 0.5 };
          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]);

  const addTrapAt = (type: SceneTrapType, nx: number, ny: number) => {
    const trap: SceneTrap = {
      id: asSceneTrapId(randomTrapId()),
      type,
      nx,
      ny,
      sizeN: DEFAULT_SCENE_TRAP_SIZE_N,
      ...(type === 'freeform' ? { label: trapTypeLabelRu(type) } : {}),
    };
    const next = [...trapsRef.current, trap];
    setSelectedId(trap.id);
    void persistTraps(next);
  };

  const onPaletteDragStart = (type: SceneTrapType) => (e: React.DragEvent) => {
    e.dataTransfer.setData('application/x-dnd-trap-type', type);
    e.dataTransfer.effectAllowed = 'copy';
  };

  const onStageDrop = (e: React.DragEvent) => {
    e.preventDefault();
    const type = e.dataTransfer.getData('application/x-dnd-trap-type') as SceneTrapType;
    if (!SCENE_TRAP_TYPES.includes(type)) return;
    const p = hostToNorm(e.clientX, e.clientY);
    if (!p) return;
    addTrapAt(type, p.x, p.y);
  };

  const updateTrap = (id: string, patch: Partial<SceneTrap>) => {
    const next = trapsRef.current.map((t) => (t.id === id ? { ...t, ...patch } : t));
    void persistTraps(next);
  };

  const isImage = scene?.previewAssetType === 'image' && Boolean(url);

  return (
    <div className={styles.page}>
      <aside className={styles.sidebar}>
        <div className={styles.sideTitle}>{scene?.title ?? 'Сцена'}</div>
        <div className={styles.hint}>
          Колесо — зум. СКМ / Space+ЛКМ — пан. Delete — удалить выбранную ловушку.
        </div>
        <div className={styles.accordion}>
          <button type="button" className={styles.accordionHead} onClick={() => setTrapsOpen((v) => !v)}>
            Ловушки {trapsOpen ? '▾' : '▸'}
          </button>
          {trapsOpen ? (
            <div className={styles.palette}>
              {SCENE_TRAP_TYPES.map((type) => (
                <div
                  key={type}
                  className={styles.paletteItem}
                  draggable
                  onDragStart={onPaletteDragStart(type)}
                  title="Перетащите на карту"
                >
                  <TrapGlyph type={type} size={22} />
                  <span className={styles.paletteLabel}>{trapTypeLabelRu(type)}</span>
                </div>
              ))}
            </div>
          ) : null}
        </div>
        {selectedId ? (
          <div className={styles.toolbar}>
            <Button
              onClick={() => {
                const next = localTraps.filter((t) => t.id !== selectedId);
                setSelectedId(null);
                void persistTraps(next);
              }}
            >
              Удалить
            </Button>
          </div>
        ) : null}
      </aside>

      <div className={styles.stage}>
        {!isImage ? (
          <div className={styles.empty}>Нужно изображение сцены</div>
        ) : (
          <div
            ref={hostRef}
            className={styles.viewport}
            onDragOver={(e) => e.preventDefault()}
            onDrop={onStageDrop}
            onPointerDown={(e) => {
              if (e.button === 1 || (e.button === 0 && spaceDownRef.current)) {
                e.preventDefault();
                (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
                dragRef.current = { kind: 'pan', lastX: e.clientX, lastY: e.clientY };
              }
            }}
            onPointerMove={(e) => {
              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 === 'move') {
                const p = hostToNorm(e.clientX, e.clientY);
                if (!p) return;
                updateTrap(d.trapId, {
                  nx: Math.max(0, Math.min(1, d.startNx + (p.x - d.pointerNx))),
                  ny: Math.max(0, Math.min(1, d.startNy + (p.y - d.pointerNy))),
                });
                return;
              }
              if (d.kind === 'resize') {
                const p = hostToNorm(e.clientX, e.clientY);
                const trap = trapsRef.current.find((t) => t.id === d.trapId);
                if (!p || !trap) return;
                const dist = Math.hypot(p.x - trap.nx, p.y - trap.ny);
                const ratio = d.startDist > 1e-6 ? dist / d.startDist : 1;
                updateTrap(d.trapId, {
                  sizeN: Math.max(0.02, Math.min(0.45, d.startSize * ratio)),
                });
              }
            }}
            onPointerUp={() => {
              dragRef.current = null;
            }}
            onPointerCancel={() => {
              dragRef.current = null;
            }}
          >
            <RotatedImage
              url={url!}
              rotationDeg={rot}
              mode="contain"
              viewCamera={viewCamera}
              onContentRectChange={setContentRect}
            />
            {contentRect
              ? localTraps.map((trap) => {
                  const minDim = Math.min(contentRect.w, contentRect.h);
                  const sizePx = Math.max(16, trap.sizeN * minDim);
                  const left = contentRect.x + trap.nx * contentRect.w;
                  const top = contentRect.y + trap.ny * contentRect.h;
                  const selected = selectedId === trap.id;
                  return (
                    <div
                      key={trap.id}
                      className={[styles.trap, selected ? styles.trapSelected : ''].filter(Boolean).join(' ')}
                      style={{ left, top, width: sizePx, height: sizePx }}
                      onPointerDown={(e) => {
                        if (e.button !== 0 || spaceDownRef.current) return;
                        e.stopPropagation();
                        setSelectedId(trap.id);
                        const p = hostToNorm(e.clientX, e.clientY);
                        if (!p) return;
                        (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
                        dragRef.current = {
                          kind: 'move',
                          trapId: trap.id,
                          startNx: trap.nx,
                          startNy: trap.ny,
                          pointerNx: p.x,
                          pointerNy: p.y,
                        };
                      }}
                    >
                      <TrapGlyph type={trap.type} size={Math.max(14, sizePx * 0.55)} />
                      {trap.label ? <div className={styles.trapLabel}>{trap.label}</div> : null}
                      {selected ? (
                        <div
                          className={styles.handle}
                          onPointerDown={(e) => {
                            e.stopPropagation();
                            e.preventDefault();
                            const p = hostToNorm(e.clientX, e.clientY);
                            if (!p) return;
                            (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
                            dragRef.current = {
                              kind: 'resize',
                              trapId: trap.id,
                              startSize: trap.sizeN,
                              startDist: Math.max(1e-4, Math.hypot(p.x - trap.nx, p.y - trap.ny)),
                            };
                          }}
                        />
                      ) : null}
                    </div>
                  );
                })
              : null}
          </div>
        )}
      </div>
    </div>
  );
}
