"use client";

import { useEffect, useMemo, useRef, useState, type ChangeEvent, type PointerEvent as ReactPointerEvent } from "react";
import type { LightingDirection, LightingPosition, LightingSettings, LightingViewMode } from "@/types";
import { DEFAULT_LIGHTING_SETTINGS, LIGHTING_PRESETS, normalizeLightingSettings } from "@/utils/lighting";

interface LightingEffectModalProps {
  isOpen: boolean;
  value?: LightingSettings | null;
  sourceImage?: string | null;
  onClose: () => void;
  onApply: (settings: LightingSettings) => void;
}

const DIRECTIONS: Array<{ id: LightingDirection; label: string }> = [
  { id: "left", label: "左侧" },
  { id: "top", label: "顶部" },
  { id: "right", label: "右侧" },
  { id: "front", label: "前方" },
  { id: "bottom", label: "底部" },
  { id: "back", label: "后方" },
];

const VIEW_MODES: Array<{ id: LightingViewMode; label: string }> = [
  { id: "perspective", label: "透视" },
  { id: "front", label: "正面" },
];

function readImageFile(file: File): Promise<string | null> {
  if (!file.type.match(/^image\/(png|jpeg|webp)$/)) return Promise.resolve(null);
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (event) => resolve((event.target?.result as string | undefined) ?? null);
    reader.onerror = () => resolve(null);
    reader.readAsDataURL(file);
  });
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(max, Math.max(min, value));
}

function formatCssNumber(value: number): string {
  const rounded = Number(value.toFixed(2));
  return Object.is(rounded, -0) ? "0" : `${rounded}`;
}

function normalizeHex(value: string): string | null {
  const normalized = value.trim().replace(/^#/, "");
  if (!/^[0-9a-fA-F]{6}$/.test(normalized)) return null;
  return `#${normalized.toLowerCase()}`;
}

function hexToRgb(hex: string): { r: number; g: number; b: number } {
  const normalized = normalizeHex(hex) ?? "#ffffff";
  return {
    r: Number.parseInt(normalized.slice(1, 3), 16),
    g: Number.parseInt(normalized.slice(3, 5), 16),
    b: Number.parseInt(normalized.slice(5, 7), 16),
  };
}

function rgbToHex(r: number, g: number, b: number): string {
  return `#${[r, g, b].map((channel) => clamp(Math.round(channel), 0, 255).toString(16).padStart(2, "0")).join("")}`;
}

function hexToHsv(hex: string): { h: number; s: number; v: number } {
  const { r, g, b } = hexToRgb(hex);
  const rn = r / 255;
  const gn = g / 255;
  const bn = b / 255;
  const max = Math.max(rn, gn, bn);
  const min = Math.min(rn, gn, bn);
  const delta = max - min;
  let h = 0;

  if (delta !== 0) {
    if (max === rn) h = ((gn - bn) / delta) % 6;
    else if (max === gn) h = (bn - rn) / delta + 2;
    else h = (rn - gn) / delta + 4;
    h *= 60;
    if (h < 0) h += 360;
  }

  return {
    h,
    s: max === 0 ? 0 : delta / max,
    v: max,
  };
}

function hsvToHex(h: number, s: number, v: number): string {
  const c = v * s;
  const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
  const m = v - c;
  let r = 0;
  let g = 0;
  let b = 0;

  if (h < 60) [r, g, b] = [c, x, 0];
  else if (h < 120) [r, g, b] = [x, c, 0];
  else if (h < 180) [r, g, b] = [0, c, x];
  else if (h < 240) [r, g, b] = [0, x, c];
  else if (h < 300) [r, g, b] = [x, 0, c];
  else [r, g, b] = [c, 0, x];

  return rgbToHex((r + m) * 255, (g + m) * 255, (b + m) * 255);
}

function directionToPosition(direction: LightingDirection): LightingPosition {
  switch (direction) {
    case "left":
      return { x: -1, y: 0, z: 0 };
    case "top":
      return { x: 0, y: -1, z: 0 };
    case "right":
      return { x: 1, y: 0, z: 0 };
    case "bottom":
      return { x: 0, y: 1, z: 0 };
    case "back":
      return { x: 0, y: 0, z: -1 };
    case "front":
    default:
      return { x: 0, y: 0, z: 1 };
  }
}

function positionToDirection(position: LightingPosition, fallback: LightingDirection = "front"): LightingDirection {
  if (position.z <= -0.95 && Math.abs(position.x) < 0.1 && Math.abs(position.y) < 0.1) return "back";
  if (position.z >= 0.95 && Math.abs(position.x) < 0.1 && Math.abs(position.y) < 0.1) return "front";
  if (position.y <= -0.95 && Math.abs(position.x) < 0.1) return "top";
  if (position.y >= 0.95 && Math.abs(position.x) < 0.1) return "bottom";
  if (position.x <= -0.95 && Math.abs(position.y) < 0.1) return "left";
  if (position.x >= 0.95 && Math.abs(position.y) < 0.1) return "right";
  return fallback;
}

function isCardinalPosition(position: LightingPosition, direction: LightingDirection): boolean {
  const target = directionToPosition(direction);
  return (
    Math.abs(position.x - target.x) < 0.05 &&
    Math.abs(position.y - target.y) < 0.05 &&
    Math.abs(position.z - target.z) < 0.05
  );
}

function normalizePosition(position: LightingPosition): LightingPosition {
  const length = Math.hypot(position.x, position.y, position.z);
  if (length === 0) return directionToPosition("front");
  return { x: position.x / length, y: position.y / length, z: position.z / length };
}

const LIGHT_STOPS: Array<{ id: string; position: LightingPosition }> = [
  { id: "top", position: { x: 0, y: -1, z: 0 } },
  { id: "bottom", position: { x: 0, y: 1, z: 0 } },
  { id: "left", position: { x: -1, y: 0, z: 0 } },
  { id: "right", position: { x: 1, y: 0, z: 0 } },
  { id: "front", position: { x: 0, y: 0, z: 1 } },
  { id: "back", position: { x: 0, y: 0, z: -1 } },
  { id: "left-top-front", position: { x: -0.58, y: -0.58, z: 0.58 } },
  { id: "left-bottom-front", position: { x: -0.58, y: 0.58, z: 0.58 } },
  { id: "left-top-back", position: { x: -0.58, y: -0.58, z: -0.58 } },
  { id: "left-bottom-back", position: { x: -0.58, y: 0.58, z: -0.58 } },
  { id: "right-top-front", position: { x: 0.58, y: -0.58, z: 0.58 } },
  { id: "right-bottom-front", position: { x: 0.58, y: 0.58, z: 0.58 } },
  { id: "right-top-back", position: { x: 0.58, y: -0.58, z: -0.58 } },
  { id: "right-bottom-back", position: { x: 0.58, y: 0.58, z: -0.58 } },
];

function projectLightPosition(position: LightingPosition, viewMode: LightingViewMode): { left: number; top: number } {
  if (viewMode === "perspective") {
    return {
      left: clamp(50 + position.x * 34 - position.z * 43, 7, 93),
      top: clamp(50 + position.y * 38 + position.z * 16, 7, 93),
    };
  }

  if (Math.abs(position.x) < 0.2 && Math.abs(position.y) < 0.2) {
    return { left: 50, top: 50 };
  }

  const distance = Math.hypot(position.x, position.y) || 1;
  const x = position.x / distance;
  const y = position.y / distance;

  return {
    left: 50 + x * 49,
    top: 50 + y * 49,
  };
}

interface PreviewPoint {
  left: number;
  top: number;
}

function getSubjectProjection(viewMode: LightingViewMode) {
  if (viewMode === "front") {
    return {
      left: 50,
      top: 50,
      rotateX: 0,
      rotateY: 0,
      rotateZ: 0,
      scale: 1,
      width: 42,
    };
  }

  return {
    left: 50,
    top: 50,
    rotateX: -8,
    rotateY: -58,
    rotateZ: 0,
    scale: 0.98,
    width: 45,
  };
}

function getBeamGeometry(source: PreviewPoint, target: PreviewPoint, viewMode: LightingViewMode) {
  const deltaX = target.left - source.left;
  const deltaY = target.top - source.top;
  const distance = Math.hypot(deltaX, deltaY);

  return {
    angle: Math.atan2(deltaY, deltaX) * (180 / Math.PI),
    length: Math.max(viewMode === "perspective" ? 60 : 54, distance * (viewMode === "perspective" ? 1.8 : 2.2)),
  };
}

function getSubjectHighlight(position: LightingPosition): PreviewPoint {
  return {
    left: clamp(50 - position.x * 28, 18, 82),
    top: clamp(50 - position.y * 28, 18, 82),
  };
}

interface SpherePointer {
  rect: DOMRect;
  clientX: number;
  clientY: number;
}

function readSpherePointer(event: ReactPointerEvent<HTMLDivElement>): SpherePointer {
  return {
    rect: event.currentTarget.getBoundingClientRect(),
    clientX: event.clientX,
    clientY: event.clientY,
  };
}

function positionFromPointer(pointer: SpherePointer, currentPosition: LightingPosition): LightingPosition {
  const { rect, clientX, clientY } = pointer;
  const radius = Math.min(rect.width, rect.height) / 2;
  const centerX = rect.left + rect.width / 2;
  const centerY = rect.top + rect.height / 2;
  let x = (clientX - centerX) / radius;
  let y = (clientY - centerY) / radius;
  const distance = Math.hypot(x, y);

  if (distance < 0.18) {
    return currentPosition.z < -0.25 ? directionToPosition("back") : directionToPosition("front");
  }

  if (distance > 1) {
    x /= distance;
    y /= distance;
  }

  const clampedDistance = Math.min(1, Math.hypot(x, y));
  const zMagnitude = Math.sqrt(Math.max(0, 1 - clampedDistance * clampedDistance));
  const zSign = currentPosition.z < -0.25 ? -1 : 1;

  return normalizePosition({ x, y, z: zMagnitude * zSign });
}

function nearestLightStop(position: LightingPosition): LightingPosition {
  if (Math.abs(position.x) < 0.2 && Math.abs(position.y) < 0.2) {
    return position.z < 0 ? directionToPosition("back") : directionToPosition("front");
  }

  const zSign = position.z < -0.2 ? -1 : 1;
  const candidates = LIGHT_STOPS.filter((stop) => {
    if (stop.id === "front" || stop.id === "back") return false;
    if (stop.position.z > 0.1) return zSign > 0;
    if (stop.position.z < -0.1) return zSign < 0;
    return true;
  });
  const projected = projectLightPosition(position, "front");

  return candidates.reduce((best, stop) => {
    const stopProjected = projectLightPosition(stop.position, "front");
    const distance = Math.hypot(projected.left - stopProjected.left, projected.top - stopProjected.top);
    return distance < best.distance ? { distance, position: stop.position } : best;
  }, { distance: Number.POSITIVE_INFINITY, position: candidates[0].position }).position;
}

function LightingPreview({
  settings,
  sourceImage,
  onLightPositionChange,
}: {
  settings: LightingSettings;
  sourceImage?: string | null;
  onLightPositionChange: (position: LightingPosition) => void;
}) {
  const [draftPosition, setDraftPosition] = useState(settings.lightPosition);
  const [isDraggingLight, setIsDraggingLight] = useState(false);
  const position = isDraggingLight ? draftPosition : settings.lightPosition;
  const projected = projectLightPosition(position, settings.viewMode);
  const subject = getSubjectProjection(settings.viewMode);
  const subjectHighlight = getSubjectHighlight(position);
  const beam = getBeamGeometry(projected, subject, settings.viewMode);
  const opacity = Math.max(0.2, Math.min(0.9, settings.brightness / 100));
  const beamSpread = Math.abs(settings.beamAngle) === 90 ? 30 : 22;
  const isPureFrontLight =
    settings.viewMode === "front" && position.z > 0.8 && Math.abs(position.x) < 0.2 && Math.abs(position.y) < 0.2;
  const isPureBackLight = position.z < -0.8 && Math.abs(position.x) < 0.2 && Math.abs(position.y) < 0.2;
  const isBackLight = position.z < -0.2;
  const subjectTransform = [
    "perspective(420px)",
    "translate(-50%, -50%)",
    `rotateX(${formatCssNumber(subject.rotateX)}deg)`,
    `rotateY(${formatCssNumber(subject.rotateY)}deg)`,
    `rotateZ(${formatCssNumber(subject.rotateZ)}deg)`,
    `scale(${formatCssNumber(subject.scale)})`,
  ].join(" ");

  useEffect(() => {
    if (!isDraggingLight) {
      setDraftPosition(settings.lightPosition);
    }
  }, [isDraggingLight, settings.lightPosition]);

  const updateDraftPosition = (event: ReactPointerEvent<HTMLDivElement>) => {
    const pointer = readSpherePointer(event);
    setDraftPosition((current) => positionFromPointer(pointer, current));
  };

  const commitDraftPosition = (event: ReactPointerEvent<HTMLDivElement>) => {
    const releasedPosition = positionFromPointer(readSpherePointer(event), draftPosition);
    const snappedPosition = nearestLightStop(releasedPosition);
    setDraftPosition(snappedPosition);
    setIsDraggingLight(false);
    onLightPositionChange(snappedPosition);
  };

  return (
    <div className="relative h-56 overflow-hidden rounded-lg bg-neutral-900">
      <div
        className="absolute left-1/2 top-1/2 h-44 w-44 -translate-x-1/2 -translate-y-1/2 touch-none overflow-hidden rounded-full border border-white/10 bg-[radial-gradient(circle_at_35%_28%,rgba(255,255,255,0.2),rgba(255,255,255,0.08)_34%,rgba(255,255,255,0.03)_72%)] shadow-inner"
        onPointerDown={(event) => {
          event.currentTarget.setPointerCapture(event.pointerId);
          const pointer = readSpherePointer(event);
          setIsDraggingLight(true);
          setDraftPosition(positionFromPointer(pointer, position));
        }}
        onPointerMove={(event) => {
          if (event.buttons !== 1) return;
          updateDraftPosition(event);
        }}
        onPointerUp={commitDraftPosition}
        onPointerCancel={commitDraftPosition}
        aria-label="拖拽光源控制点"
      >
        <div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.08)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.08)_1px,transparent_1px)] bg-[size:43px_43px] opacity-35" />
        <div className="absolute left-0 top-1/2 h-px w-full bg-white/10" />
        <div className="absolute left-1/2 top-0 h-full w-px bg-white/10" />
        <div className="absolute left-[12%] top-[23%] h-[54%] w-[76%] rounded-[50%] border border-white/10" />
        <div className="absolute left-[6%] top-[36%] h-[28%] w-[88%] rounded-[50%] border border-white/8" />

        {isPureFrontLight && (
          <div
            className="absolute z-[5] h-24 w-24 -translate-x-1/2 -translate-y-1/2 rounded-full blur-sm"
            style={{
              left: `${projected.left}%`,
              top: `${projected.top}%`,
              background: `radial-gradient(circle, ${settings.color}dd 0%, ${settings.color}55 42%, transparent 72%)`,
              opacity,
            }}
          />
        )}

        {!isPureFrontLight && !isPureBackLight && (
          <div
            data-testid="lighting-preview-beam"
            className="absolute z-[5] origin-left blur-[1px]"
            style={{
              left: `${projected.left}%`,
              top: `${projected.top}%`,
              width: `${formatCssNumber(beam.length)}%`,
              height: `${beamSpread}%`,
              background: `linear-gradient(90deg, ${settings.color}ee, ${settings.color}66 46%, transparent 88%)`,
              clipPath: "polygon(0 50%, 100% 8%, 100% 92%)",
              opacity: isBackLight ? opacity * 0.55 : opacity,
              transform: `translateY(-50%) rotate(${formatCssNumber(beam.angle)}deg)`,
            }}
          />
        )}

        <div
          data-testid="lighting-preview-subject"
          className={`absolute z-20 aspect-square overflow-hidden rounded-sm border border-white/20 bg-neutral-700 shadow-2xl ${
            settings.rimLight ? "ring-2 ring-cyan-200/80" : ""
          }`}
          style={{
            left: `${formatCssNumber(subject.left)}%`,
            top: `${formatCssNumber(subject.top)}%`,
            width: `${subject.width}%`,
            transform: subjectTransform,
            transformStyle: "preserve-3d",
          }}
        >
          {sourceImage ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={sourceImage} alt="" className="h-full w-full object-cover" />
          ) : (
            <div className="h-full w-full bg-gradient-to-b from-neutral-500 to-neutral-800" />
          )}
          <div
            className="pointer-events-none absolute inset-0 mix-blend-screen"
            style={{
              background: `radial-gradient(circle at ${subjectHighlight.left}% ${subjectHighlight.top}%, ${settings.color}aa 0%, ${settings.color}55 34%, transparent 72%)`,
              opacity: isBackLight ? opacity * 0.18 : opacity * 0.55,
            }}
          />
        </div>

        {!isPureBackLight && (
          <div
            className="absolute z-30 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-black shadow-[0_0_0_1px_rgba(255,255,255,0.35),0_0_16px_rgba(255,255,255,0.9)]"
            style={{
              left: `${projected.left}%`,
              top: `${projected.top}%`,
            }}
          />
        )}
      </div>
    </div>
  );
}

function LightingColorPicker({
  value,
  onChange,
}: {
  value: string;
  onChange: (value: string) => void;
}) {
  const [open, setOpen] = useState(false);
  const hsv = useMemo(() => hexToHsv(value), [value]);
  const [hue, setHue] = useState(hsv.h);
  const pickerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (hsv.s > 0) {
      setHue(hsv.h);
    }
  }, [hsv.h, hsv.s]);

  useEffect(() => {
    if (!open) return;

    const handlePointerDown = (event: PointerEvent) => {
      const target = event.target;
      if (target instanceof Node && pickerRef.current?.contains(target)) return;
      setOpen(false);
    };

    document.addEventListener("pointerdown", handlePointerDown);
    return () => document.removeEventListener("pointerdown", handlePointerDown);
  }, [open]);

  const updateSaturationValue = (event: ReactPointerEvent<HTMLDivElement>) => {
    const rect = event.currentTarget.getBoundingClientRect();
    const s = clamp((event.clientX - rect.left) / rect.width, 0, 1);
    const v = clamp(1 - (event.clientY - rect.top) / rect.height, 0, 1);
    onChange(hsvToHex(hue, s, v));
  };

  const updateHue = (nextHue: number) => {
    const normalizedHue = clamp(nextHue, 0, 360);
    setHue(normalizedHue);
    onChange(hsvToHex(normalizedHue, hsv.s, hsv.v));
  };

  const updateHueFromPointer = (event: ReactPointerEvent<HTMLDivElement>) => {
    const rect = event.currentTarget.getBoundingClientRect();
    const ratio = rect.width > 0 ? clamp((event.clientX - rect.left) / rect.width, 0, 1) : 0;
    updateHue(ratio * 360);
  };

  const handleHexChange = (nextValue: string) => {
    const normalized = normalizeHex(nextValue);
    if (normalized) onChange(normalized);
  };

  return (
    <div ref={pickerRef} className="relative">
      <button
        type="button"
        onClick={() => setOpen((current) => !current)}
        className="h-8 w-12 rounded border border-neutral-700 bg-neutral-900 p-1 transition-colors hover:border-neutral-500"
        aria-label="打光颜色"
      >
        <span className="block h-full w-full rounded-sm" style={{ backgroundColor: value }} />
      </button>

      {open && (
        <div
          data-testid="lighting-color-picker-popover"
          className="absolute left-0 top-10 z-[70] w-44 rounded-lg border border-neutral-600 bg-neutral-900 p-2 shadow-2xl shadow-black/60"
        >
          <div
            className="relative h-28 cursor-crosshair overflow-hidden rounded-md"
            style={{
              backgroundColor: hsvToHex(hue, 1, 1),
              backgroundImage:
                "linear-gradient(90deg, #fff, rgba(255,255,255,0)), linear-gradient(0deg, #000, rgba(0,0,0,0))",
            }}
            onPointerDown={(event) => {
              event.currentTarget.setPointerCapture?.(event.pointerId);
              updateSaturationValue(event);
            }}
            onPointerMove={(event) => {
              if (event.buttons !== 1) return;
              updateSaturationValue(event);
            }}
          >
            <span
              className="absolute h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_rgba(0,0,0,0.7)]"
              style={{ left: `${hsv.s * 100}%`, top: `${(1 - hsv.v) * 100}%` }}
            />
          </div>

          <div
            role="slider"
            tabIndex={0}
            aria-valuemin={0}
            aria-valuemax={360}
            aria-valuenow={Math.round(hue)}
            aria-label="打光颜色色相"
            className="relative mt-2 h-3 w-full cursor-pointer rounded-full bg-[linear-gradient(90deg,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)] outline-none focus:ring-2 focus:ring-white/30"
            onPointerDown={(event) => {
              event.currentTarget.setPointerCapture?.(event.pointerId);
              updateHueFromPointer(event);
            }}
            onPointerMove={(event) => {
              if (event.buttons !== 1) return;
              updateHueFromPointer(event);
            }}
            onKeyDown={(event) => {
              if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
                event.preventDefault();
                updateHue(hue - 5);
              }
              if (event.key === "ArrowRight" || event.key === "ArrowUp") {
                event.preventDefault();
                updateHue(hue + 5);
              }
            }}
          >
            <span
              data-testid="lighting-hue-thumb"
              className="absolute top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.45)]"
              style={{ left: `${(hue / 360) * 100}%` }}
            />
          </div>

          <input
            type="text"
            value={value}
            onChange={(event) => handleHexChange(event.target.value)}
            className="mt-2 h-8 w-full rounded-md border border-neutral-600 bg-neutral-800 px-2 text-xs text-neutral-100 outline-none focus:border-neutral-400"
            aria-label="打光颜色十六进制"
          />
        </div>
      )}
    </div>
  );
}

export function LightingEffectModal({
  isOpen,
  value,
  sourceImage,
  onClose,
  onApply,
}: LightingEffectModalProps) {
  const [settings, setSettings] = useState<LightingSettings>(() => normalizeLightingSettings(value));
  const [modalPosition, setModalPosition] = useState<{ x: number; y: number } | null>(null);
  const [isDragging, setIsDragging] = useState(false);
  const referenceInputRef = useRef<HTMLInputElement>(null);
  const modalRef = useRef<HTMLDivElement>(null);
  const dragOffsetRef = useRef({ x: 0, y: 0 });

  useEffect(() => {
    if (isOpen) {
      setSettings(normalizeLightingSettings(value));
      setModalPosition(null);
      setIsDragging(false);
    }
  }, [isOpen, value]);

  const modalWidthClass = settings.smartMode ? "max-w-[760px]" : "max-w-[458px]";
  const appliedPreviewImage = settings.referenceImage || sourceImage;

  const selectedPreset = useMemo(
    () => LIGHTING_PRESETS.find((preset) => preset.id === settings.selectedPresetId),
    [settings.selectedPresetId]
  );

  if (!isOpen) return null;

  const patchSettings = (patch: Partial<LightingSettings>) => {
    setSettings((current) => ({ ...current, ...patch, enabled: true }));
  };

  const handleReferenceChange = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    event.target.value = "";
    if (!file) return;
    const image = await readImageFile(file);
    if (image) patchSettings({ referenceImage: image });
  };

  const handleDragStart = (event: ReactPointerEvent<HTMLDivElement>) => {
    const target = event.target as HTMLElement;
    if (target.closest("button")) return;

    const rect = modalRef.current?.getBoundingClientRect();
    if (!rect) return;

    event.currentTarget.setPointerCapture(event.pointerId);
    dragOffsetRef.current = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
    };
    setModalPosition({ x: rect.left, y: rect.top });
    setIsDragging(true);
  };

  const handleDragMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (!isDragging) return;
    const rect = modalRef.current?.getBoundingClientRect();
    const width = rect?.width ?? 460;
    const height = rect?.height ?? 420;
    setModalPosition({
      x: clamp(event.clientX - dragOffsetRef.current.x, 8, window.innerWidth - width - 8),
      y: clamp(event.clientY - dragOffsetRef.current.y, 8, window.innerHeight - height - 8),
    });
  };

  const handleDragEnd = () => {
    setIsDragging(false);
  };

  return (
    <div className="fixed inset-0 z-50 bg-black/20" role="dialog" aria-modal="true">
      <div
        ref={modalRef}
        className={`fixed w-[calc(100vw-2rem)] ${modalWidthClass} overflow-visible rounded-xl border border-neutral-700 bg-neutral-900 shadow-2xl shadow-black/50`}
        style={{
          left: modalPosition ? modalPosition.x : "50%",
          top: modalPosition ? modalPosition.y : "50%",
          transform: modalPosition ? undefined : "translate(-50%, -50%)",
        }}
      >
        <div
          className={`flex h-14 cursor-move touch-none items-center justify-between rounded-t-xl border-b border-neutral-800 px-4 ${
            isDragging ? "select-none" : ""
          }`}
          onPointerDown={handleDragStart}
          onPointerMove={handleDragMove}
          onPointerUp={handleDragEnd}
          onPointerCancel={handleDragEnd}
        >
          <h2 className="text-sm font-semibold text-neutral-100">打光效果</h2>
          <button
            type="button"
            onClick={onClose}
            className="flex h-8 w-8 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-100"
            aria-label="关闭打光效果"
          >
            ×
          </button>
        </div>

        <div className={`grid gap-4 p-4 ${settings.smartMode ? "md:grid-cols-[200px_190px_1fr]" : "md:grid-cols-[200px_190px]"}`}>
          <div className="space-y-3">
            <div className="grid grid-cols-2 rounded-xl border border-neutral-700 bg-neutral-900 p-1">
              {VIEW_MODES.map((mode) => (
                <button
                  key={mode.id}
                  type="button"
                  onClick={() => patchSettings({ viewMode: mode.id })}
                  className={`rounded-lg px-3 py-2 text-sm transition-colors ${
                    settings.viewMode === mode.id ? "bg-neutral-700 text-white" : "text-neutral-500 hover:text-neutral-200"
                  }`}
                >
                  {mode.label}
                </button>
              ))}
            </div>
            <LightingPreview
              settings={settings}
              sourceImage={appliedPreviewImage}
              onLightPositionChange={(lightPosition) =>
                patchSettings({
                  lightPosition,
                  mainLightDirection: positionToDirection(lightPosition, settings.mainLightDirection),
                })
              }
            />
            <button
              type="button"
              onClick={() => setSettings(DEFAULT_LIGHTING_SETTINGS)}
              className="text-xs text-neutral-400 transition-colors hover:text-neutral-100"
            >
              重置参数
            </button>
          </div>

          <div className="space-y-4 text-sm">
            <div className="flex items-center justify-between">
              <span className="font-semibold text-neutral-100">全局</span>
              <label className="flex items-center gap-2 text-xs text-neutral-400">
                智能模式
                <input
                  type="checkbox"
                  checked={settings.smartMode}
                  onChange={(event) => patchSettings({ smartMode: event.target.checked })}
                  className="h-4 w-4 accent-neutral-200"
                />
              </label>
            </div>

            <label className="block space-y-2">
              <span className="text-neutral-400">亮度</span>
              <div className="flex items-center gap-3">
                <input
                  type="range"
                  min={0}
                  max={100}
                  value={settings.brightness}
                  onChange={(event) => patchSettings({ brightness: Number(event.target.value) })}
                  className="w-full accent-neutral-200"
                  aria-label="打光亮度"
                />
                <span className="w-12 rounded-md border border-neutral-700 px-2 py-1 text-center text-xs text-neutral-400">
                  {settings.brightness}%
                </span>
              </div>
            </label>

            <div className="flex items-center justify-between gap-3">
              <span className="text-neutral-400">颜色</span>
              <LightingColorPicker
                value={settings.color}
                onChange={(color) => patchSettings({ color })}
              />
            </div>

            <div className="space-y-2">
              <span className="text-neutral-400">主光源</span>
              <div className="grid grid-cols-3 gap-2">
                {DIRECTIONS.map((direction) => (
                  <button
                    key={direction.id}
                    type="button"
                    onClick={() =>
                      patchSettings({
                        mainLightDirection: direction.id,
                        lightPosition: directionToPosition(direction.id),
                      })
                    }
                    className={`rounded-lg border px-3 py-2 text-xs transition-colors ${
                      isCardinalPosition(settings.lightPosition, direction.id)
                        ? "border-neutral-400 bg-neutral-600 text-white"
                        : "border-neutral-700 bg-neutral-900 text-neutral-400 hover:border-neutral-500 hover:text-neutral-100"
                    }`}
                  >
                    {direction.label}
                  </button>
                ))}
              </div>
            </div>

            <label className="flex items-center justify-between gap-3">
              <span className="text-neutral-400">轮廓光</span>
              <input
                type="checkbox"
                checked={settings.rimLight}
                onChange={(event) => patchSettings({ rimLight: event.target.checked })}
                className="h-4 w-4 accent-neutral-200"
                aria-label="轮廓光"
              />
            </label>
          </div>

          {settings.smartMode && (
            <div className="space-y-4 border-t border-neutral-800 pt-4 md:border-l md:border-t-0 md:pl-4 md:pt-0">
              <div className="text-sm font-semibold text-neutral-100">智能模式</div>
              <div className="grid grid-cols-[1fr_64px] gap-3">
                <textarea
                  value={settings.smartPrompt}
                  onChange={(event) => patchSettings({ smartPrompt: event.target.value })}
                  placeholder="简单描述你想实现的打光效果，或者情绪风格"
                  className="h-20 resize-none rounded-lg border border-neutral-700 bg-neutral-900 px-3 py-2 text-xs leading-5 text-neutral-100 outline-none placeholder:text-neutral-500 focus:border-neutral-500"
                />
                <button
                  type="button"
                  onClick={() => referenceInputRef.current?.click()}
                  className="rounded-lg border border-dashed border-neutral-700 bg-neutral-900 px-2 text-xs text-neutral-400 transition-colors hover:border-neutral-500 hover:text-neutral-100"
                >
                  +<br />打光参考图
                </button>
              </div>
              <input
                ref={referenceInputRef}
                type="file"
                accept="image/png,image/jpeg,image/webp"
                className="hidden"
                onChange={handleReferenceChange}
                aria-label="上传打光参考图"
              />

              <div className="space-y-2">
                <div className="text-xs text-neutral-400">预设</div>
                <div className="grid grid-cols-4 gap-2">
                  {LIGHTING_PRESETS.map((preset) => (
                    <button
                      key={preset.id}
                      type="button"
                      onClick={() =>
                        patchSettings({
                          ...preset.settings,
                          selectedPresetId: preset.id,
                        })
                      }
                      className={`relative h-16 overflow-hidden rounded-lg border text-xs font-semibold text-white shadow-inner transition-transform hover:scale-[1.02] ${
                        selectedPreset?.id === preset.id ? "border-white" : "border-neutral-700"
                      }`}
                      style={{ background: preset.thumbnail }}
                    >
                      <span className="absolute inset-x-0 bottom-0 bg-black/45 px-1 py-1">{preset.label}</span>
                    </button>
                  ))}
                </div>
              </div>
            </div>
          )}
        </div>

        <div className="flex items-center justify-between border-t border-neutral-800 px-4 py-3">
          <span className="text-xs text-neutral-500">↯ 14</span>
          <button
            type="button"
            onClick={() => onApply({ ...settings, enabled: true })}
            className="flex h-9 w-9 items-center justify-center rounded-lg bg-neutral-200 text-neutral-950 transition-colors hover:bg-white"
            aria-label="应用打光效果"
          >
            <svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4">
              <path d="M12 19V5" />
              <path d="m5 12 7-7 7 7" />
            </svg>
          </button>
        </div>
      </div>
    </div>
  );
}
