import {
  type CSSProperties,
  type PointerEvent,
  useMemo,
  useRef,
  useState,
} from 'react';

export type SquareImageCropRect = {
  x: number;
  y: number;
  size: number;
};

export type SquareImageCropModalLabels = {
  title: string;
  close: string;
  editor: string;
  previewAlt: string;
  cancel: string;
  submit: string;
  saving: string;
};

type SquareCropDragHandle =
  | 'move'
  | 'north'
  | 'northEast'
  | 'east'
  | 'southEast'
  | 'south'
  | 'southWest'
  | 'west'
  | 'northWest';

type SquareCropDragSnapshot = {
  pointerId: number;
  handle: SquareCropDragHandle;
  clientX: number;
  clientY: number;
  cropRect: SquareImageCropRect;
  previewWidth: number;
  previewHeight: number;
};

const SQUARE_CROP_RESIZE_HANDLES: Array<{
  handle: Exclude<SquareCropDragHandle, 'move'>;
  label: string;
  className: string;
}> = [
  {
    handle: 'northWest',
    label: '拖拽左上角裁剪边界',
    className:
      'left-0 top-0 -translate-x-1/2 -translate-y-1/2 cursor-nwse-resize',
  },
  {
    handle: 'north',
    label: '拖拽上边裁剪边界',
    className:
      'left-1/2 top-0 -translate-x-1/2 -translate-y-1/2 cursor-ns-resize',
  },
  {
    handle: 'northEast',
    label: '拖拽右上角裁剪边界',
    className:
      'right-0 top-0 translate-x-1/2 -translate-y-1/2 cursor-nesw-resize',
  },
  {
    handle: 'east',
    label: '拖拽右边裁剪边界',
    className:
      'right-0 top-1/2 -translate-y-1/2 translate-x-1/2 cursor-ew-resize',
  },
  {
    handle: 'southEast',
    label: '拖拽右下角裁剪边界',
    className:
      'bottom-0 right-0 translate-x-1/2 translate-y-1/2 cursor-nwse-resize',
  },
  {
    handle: 'south',
    label: '拖拽下边裁剪边界',
    className:
      'bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 cursor-ns-resize',
  },
  {
    handle: 'southWest',
    label: '拖拽左下角裁剪边界',
    className:
      'bottom-0 left-0 -translate-x-1/2 translate-y-1/2 cursor-nesw-resize',
  },
  {
    handle: 'west',
    label: '拖拽左边裁剪边界',
    className:
      'left-0 top-1/2 -translate-x-1/2 -translate-y-1/2 cursor-ew-resize',
  },
];

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

function getSquareCropSizeBounds(imageSize: { width: number; height: number }) {
  const maxSize = Math.max(1, Math.min(imageSize.width, imageSize.height));
  const minSize = Math.min(maxSize, Math.max(48, maxSize * 0.18));

  return { minSize, maxSize };
}

export function buildCenteredSquareImageCropRect(imageSize: {
  width: number;
  height: number;
}): SquareImageCropRect {
  const size = Math.max(1, Math.min(imageSize.width, imageSize.height));

  return {
    x: Math.max(0, (imageSize.width - size) / 2),
    y: Math.max(0, (imageSize.height - size) / 2),
    size,
  };
}

export function clampSquareImageCropRect(
  imageSize: { width: number; height: number },
  crop: SquareImageCropRect,
): SquareImageCropRect {
  const { minSize, maxSize } = getSquareCropSizeBounds(imageSize);
  const size = clampNumber(crop.size, minSize, maxSize);

  return {
    x: clampNumber(crop.x, 0, Math.max(0, imageSize.width - size)),
    y: clampNumber(crop.y, 0, Math.max(0, imageSize.height - size)),
    size,
  };
}

function buildSquareCropPreviewStyle(
  crop: SquareImageCropRect,
  imageSize: { width: number; height: number },
) {
  return {
    left: `${(crop.x / imageSize.width) * 100}%`,
    top: `${(crop.y / imageSize.height) * 100}%`,
    width: `${(crop.size / imageSize.width) * 100}%`,
    height: `${(crop.size / imageSize.height) * 100}%`,
  } satisfies CSSProperties;
}

function resizeSquareCropRectFromHandle(
  snapshot: SquareCropDragSnapshot,
  deltaX: number,
  deltaY: number,
  imageSize: { width: number; height: number },
) {
  const start = snapshot.cropRect;
  const startRight = start.x + start.size;
  const startBottom = start.y + start.size;
  const startCenterX = start.x + start.size / 2;
  const startCenterY = start.y + start.size / 2;
  const { minSize, maxSize } = getSquareCropSizeBounds(imageSize);
  const chooseSize = (sizeFromX: number, sizeFromY: number) => {
    const xDistance = Math.abs(sizeFromX - start.size);
    const yDistance = Math.abs(sizeFromY - start.size);

    return xDistance >= yDistance ? sizeFromX : sizeFromY;
  };
  const clampSize = (size: number, maxByAnchor = maxSize) =>
    clampNumber(
      size,
      minSize,
      Math.max(minSize, Math.min(maxSize, maxByAnchor)),
    );

  if (snapshot.handle === 'move') {
    return clampSquareImageCropRect(imageSize, {
      ...start,
      x: start.x + deltaX,
      y: start.y + deltaY,
    });
  }

  // 中文注释：边缘手柄保持正方形比例，锚点逻辑与拼图图片上传裁剪一致。
  if (snapshot.handle === 'east' || snapshot.handle === 'west') {
    const isEast = snapshot.handle === 'east';
    const anchorX = isEast ? start.x : startRight;
    const maxByAnchorX = isEast ? imageSize.width - anchorX : anchorX;
    const maxByCenterY =
      2 * Math.min(startCenterY, imageSize.height - startCenterY);
    const size = clampSize(
      start.size + (isEast ? deltaX : -deltaX),
      Math.min(maxByAnchorX, maxByCenterY),
    );

    return clampSquareImageCropRect(imageSize, {
      x: isEast ? anchorX : anchorX - size,
      y: startCenterY - size / 2,
      size,
    });
  }

  if (snapshot.handle === 'north' || snapshot.handle === 'south') {
    const isSouth = snapshot.handle === 'south';
    const anchorY = isSouth ? start.y : startBottom;
    const maxByAnchorY = isSouth ? imageSize.height - anchorY : anchorY;
    const maxByCenterX =
      2 * Math.min(startCenterX, imageSize.width - startCenterX);
    const size = clampSize(
      start.size + (isSouth ? deltaY : -deltaY),
      Math.min(maxByAnchorY, maxByCenterX),
    );

    return clampSquareImageCropRect(imageSize, {
      x: startCenterX - size / 2,
      y: isSouth ? anchorY : anchorY - size,
      size,
    });
  }

  const isEast =
    snapshot.handle === 'northEast' || snapshot.handle === 'southEast';
  const isSouth =
    snapshot.handle === 'southEast' || snapshot.handle === 'southWest';
  const anchorX = isEast ? start.x : startRight;
  const anchorY = isSouth ? start.y : startBottom;
  const maxByAnchorX = isEast ? imageSize.width - anchorX : anchorX;
  const maxByAnchorY = isSouth ? imageSize.height - anchorY : anchorY;
  const sizeFromX = start.size + (isEast ? deltaX : -deltaX);
  const sizeFromY = start.size + (isSouth ? deltaY : -deltaY);
  const size = clampSize(
    chooseSize(sizeFromX, sizeFromY),
    Math.min(maxByAnchorX, maxByAnchorY),
  );

  return clampSquareImageCropRect(imageSize, {
    x: isEast ? anchorX : anchorX - size,
    y: isSouth ? anchorY : anchorY - size,
    size,
  });
}

export function SquareImageCropModal({
  source,
  imageSize,
  cropRect,
  labels,
  titleId = 'square-image-crop-title',
  error = null,
  isSaving = false,
  onCropRectChange,
  onClose,
  onSubmit,
}: {
  source: string;
  imageSize: { width: number; height: number };
  cropRect: SquareImageCropRect;
  labels: SquareImageCropModalLabels;
  titleId?: string;
  error?: string | null;
  isSaving?: boolean;
  onCropRectChange: (nextCrop: SquareImageCropRect) => void;
  onClose: () => void;
  onSubmit: () => void;
}) {
  const previewRef = useRef<HTMLDivElement | null>(null);
  const dragSnapshotRef = useRef<SquareCropDragSnapshot | null>(null);
  const [activeDragHandle, setActiveDragHandle] =
    useState<SquareCropDragHandle | null>(null);
  const normalizedCropRect = useMemo(
    () => clampSquareImageCropRect(imageSize, cropRect),
    [cropRect, imageSize],
  );
  const previewStyle = useMemo(
    () => buildSquareCropPreviewStyle(normalizedCropRect, imageSize),
    [normalizedCropRect, imageSize],
  );
  const editorPreviewStyle = useMemo(
    () =>
      ({
        aspectRatio: `${imageSize.width} / ${imageSize.height}`,
        width: `min(100%, calc(min(52vh, 22rem) * ${
          imageSize.width / Math.max(1, imageSize.height)
        }))`,
      }) satisfies CSSProperties,
    [imageSize],
  );

  const beginCropDrag = (
    handle: SquareCropDragHandle,
    event: PointerEvent<HTMLElement>,
  ) => {
    if (isSaving) {
      return;
    }

    const preview = previewRef.current;
    if (!preview) {
      return;
    }

    const rect = preview.getBoundingClientRect();
    dragSnapshotRef.current = {
      pointerId: event.pointerId,
      handle,
      clientX: event.clientX,
      clientY: event.clientY,
      cropRect: normalizedCropRect,
      previewWidth: rect.width,
      previewHeight: rect.height,
    };
    setActiveDragHandle(handle);
    event.preventDefault();
    event.stopPropagation();
    event.currentTarget.setPointerCapture(event.pointerId);
  };

  const updateCropDrag = (event: PointerEvent<HTMLElement>) => {
    const snapshot = dragSnapshotRef.current;
    if (!snapshot || snapshot.pointerId !== event.pointerId) {
      return;
    }

    const deltaX =
      ((event.clientX - snapshot.clientX) * imageSize.width) /
      Math.max(1, snapshot.previewWidth);
    const deltaY =
      ((event.clientY - snapshot.clientY) * imageSize.height) /
      Math.max(1, snapshot.previewHeight);
    onCropRectChange(
      resizeSquareCropRectFromHandle(snapshot, deltaX, deltaY, imageSize),
    );
  };

  const stopCropDrag = (event: PointerEvent<HTMLElement>) => {
    if (dragSnapshotRef.current?.pointerId !== event.pointerId) {
      return;
    }

    dragSnapshotRef.current = null;
    setActiveDragHandle(null);
    event.currentTarget.releasePointerCapture(event.pointerId);
  };

  return (
    <div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
      <div
        role="dialog"
        aria-modal="true"
        aria-labelledby={titleId}
        className="platform-modal-shell platform-remap-surface w-full max-w-sm overflow-hidden rounded-[1.4rem]"
      >
        <div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
          <div id={titleId} className="text-base font-black">
            {labels.title}
          </div>
          <button
            type="button"
            aria-label={labels.close}
            onClick={onClose}
            className="platform-profile-icon-button flex h-8 w-8 items-center justify-center rounded-full"
          >
            ×
          </button>
        </div>
        <div className="px-5 py-5">
          <div
            ref={previewRef}
            className="relative mx-auto overflow-hidden rounded-[1.2rem] border border-white/12 bg-black/35 select-none touch-none"
            style={editorPreviewStyle}
            aria-label={labels.editor}
          >
            <img
              src={source}
              alt={labels.previewAlt}
              draggable={false}
              className="h-full w-full object-fill"
            />
            <div
              className={`absolute border-2 border-sky-200/95 shadow-[0_0_0_9999px_rgba(0,0,0,0.38)] outline outline-1 outline-black/35 ${
                activeDragHandle === 'move' ? 'cursor-grabbing' : 'cursor-grab'
              }`}
              style={previewStyle}
              onPointerDown={(event) => beginCropDrag('move', event)}
              onPointerMove={updateCropDrag}
              onPointerUp={stopCropDrag}
              onPointerCancel={stopCropDrag}
            />
            <div
              className="pointer-events-none absolute border border-white/70"
              style={previewStyle}
            >
              <div className="absolute inset-x-0 top-1/3 border-t border-white/35" />
              <div className="absolute inset-x-0 top-2/3 border-t border-white/35" />
              <div className="absolute inset-y-0 left-1/3 border-l border-white/35" />
              <div className="absolute inset-y-0 left-2/3 border-l border-white/35" />
            </div>
            <div className="pointer-events-none absolute" style={previewStyle}>
              {SQUARE_CROP_RESIZE_HANDLES.map((handleConfig) => (
                <button
                  key={handleConfig.handle}
                  type="button"
                  aria-label={handleConfig.label}
                  disabled={isSaving}
                  className={`pointer-events-auto absolute z-10 h-10 w-10 rounded-full border border-white/15 bg-black/5 p-0 disabled:cursor-not-allowed disabled:opacity-45 ${handleConfig.className}`}
                  onPointerDown={(event) =>
                    beginCropDrag(handleConfig.handle, event)
                  }
                  onPointerMove={updateCropDrag}
                  onPointerUp={stopCropDrag}
                  onPointerCancel={stopCropDrag}
                >
                  <span className="absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white bg-sky-300 shadow-[0_0_0_3px_rgba(2,132,199,0.32)]" />
                </button>
              ))}
            </div>
          </div>
          {error ? (
            <div className="mt-4 rounded-2xl border border-rose-400/25 bg-rose-500/10 px-3 py-2 text-sm text-rose-600">
              {error}
            </div>
          ) : null}
          <div className="mt-5 grid grid-cols-2 gap-3">
            <button
              type="button"
              onClick={onClose}
              className="platform-button platform-button--secondary justify-center"
            >
              {labels.cancel}
            </button>
            <button
              type="button"
              onClick={onSubmit}
              disabled={isSaving}
              className="platform-button platform-button--primary justify-center disabled:cursor-not-allowed disabled:opacity-60"
            >
              {isSaving ? labels.saving : labels.submit}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}
