import { forwardRef, memo } from 'react';
import { type PageData } from '../types';
import type { EditorObject } from '@/pages/editor/store/editorStore';
import { toCssLinearGradient, getSvgGradientEndpoints } from '@/pages/editor/utils/gradient';
import { getCustomShapePointBounds } from '@/pages/editor/utils/customShape';
import { getFontFamily } from '@/pages/editor/utils/fontFamily';
import {
  DEFAULT_TEXT_MAX_WIDTH_PX,
  getCssFontWeight,
  getViewerTextLayout,
  resolveTextMaxWidth,
  usesWrappedLayout,
} from '@/pages/editor/utils/textStyle';
import { getCssBlurStyle } from '@/pages/editor/utils/shapeBlur';
import { getCssBorderRadius } from '@/pages/editor/utils/borderRadius';
import '@/pages/viewer/styles/entranceAnimations.css';
import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimationStyle';
import { getMaskImageStyle, getMaskedLayout } from '@/pages/viewer/utils/maskStyle';
import type { EntrancePhase } from '@/pages/viewer/hooks/useBookEntranceController';

const getRasterObjectStyle = (
  obj: EditorObject,
  scale: number,
  baseStyle: React.CSSProperties,
  index: number,
): React.CSSProperties => {
  const scaleX = obj.scaleX ?? 1;
  const scaleY = obj.scaleY ?? 1;
  const width = obj.width != null ? obj.width * scaleX * scale : undefined;
  const height = obj.height != null ? obj.height * scaleY * scale : undefined;
  const borderRadius = getCssBorderRadius(obj.borderRadius, scale);

  return {
    ...baseStyle,
    display: 'block',
    maxWidth: 'none',
    width: width != null ? `${width}px` : 'auto',
    height: height != null ? `${height}px` : 'auto',
    objectFit: 'fill',
    zIndex: index,
    ...(borderRadius ? { borderRadius, overflow: 'hidden' as const } : {}),
  };
};

type RenderObjectOptions = {
  skipEntrance?: boolean;
  /** در پیش‌نمایش (لیست صفحات و کاتالوگ) رسانه تعاملی نباشد */
  staticMedia?: boolean;
};

type BookPageProps = {
  page: PageData;
  scale?: number;
  pageWidth?: number;
  pageHeight?: number;
  onLinkClick?: (linkUrl: string) => void;
  backgroundType?: 'color' | 'gradient' | 'image' | 'video';
  backgroundColor?: string;
  backgroundGradient?: {
    from: string;
    to: string;
    angle: number;
  };
  backgroundImageUrl?: string;
  backgroundVideoUrl?: string;
  /** فاز انیمیشن ورود — از BookViewer کنترل می‌شود */
  entrancePhase?: EntrancePhase;
  /** در پیش‌نمایش کاتالوگ انیمی션 ورود غیرفعال باشد */
  disableEntranceAnimations?: boolean;
  /** ویدیو/صوت فقط به‌صورت کاور — بدون کنترل و بدون کلیک */
  staticMedia?: boolean;
  /** سایهٔ لبهٔ کاغذ (گرادیان داخل DOM — سازگار با iOS قدیمی) */
  showPaperShadow?: boolean;
  /** ورق سخت به‌جای clip-path برای iOS قدیمی */
  useHardPage?: boolean;
  /**
   * پسوند یکتا برای idهای SVG (گرادیان و غیره).
   * وقتی همان صفحه بیش از یک‌بار هم‌زمان رندر می‌شود (مثلاً در Magnifier)
   * باید idها متفاوت باشند تا با نسخهٔ اصلی تداخل نکنند.
   */
  idSuffix?: string;
};

/**
 * کامپوننت خالص برای نمایش محتوای یک صفحه کتاب
 * تمام المنت‌ها با absolute positioning رندر می‌شوند
 * از HTML استفاده می‌شود (نه Canvas) برای سازگاری با Konva در آینده
 * این کامپوننت به عنوان child برای HTMLFlipBook استفاده می‌شود
 * نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
 */
const BookPage = memo(forwardRef<HTMLDivElement, BookPageProps>(
  ({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl, backgroundVideoUrl, entrancePhase = 'idle', disableEntranceAnimations = false, staticMedia = false, showPaperShadow = true, useHardPage = false, idSuffix = '' }, ref) => {
    const phase: EntrancePhase = disableEntranceAnimations ? 'settled' : entrancePhase;
    // تابع برای تبدیل opacity به رنگ (مثل editor)
    // در editor، getColorWithOpacity انتظار opacity 0-100 دارد
    // در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است
    // پس opacity را مستقیماً استفاده می‌کنیم
    const getColorWithOpacity = (fill?: string, opacity?: number): string => {
      if (!fill) return '#000000';

      // opacity در EditorObject به صورت 0-100 است (بعد از تبدیل در dataTransformer)
      // در editor getColorWithOpacity انتظار 0-100 دارد
      const opacityValue = opacity !== undefined ? opacity : 100;

      // اگر opacity 100% باشد، رنگ را بدون تغییر برگردان (مثل editor)
      if (opacityValue >= 100) return fill;

      const hex = fill.replace('#', '');
      // اگر hex کوتاه‌تر از 6 کاراکتر باشد، padding اضافه کن
      const paddedHex = hex.length === 3
        ? hex.split('').map(char => char + char).join('')
        : hex;

      const r = parseInt(paddedHex.substring(0, 2), 16);
      const g = parseInt(paddedHex.substring(2, 4), 16);
      const b = parseInt(paddedHex.substring(4, 6), 16);
      // opacityValue را به alpha تبدیل کن (0-100 -> 0-1) مثل editor
      const alpha = opacityValue / 100;

      return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    };

    const flyLayoutPx = {
      width: pageWidth ?? 794 * scale,
      height: pageHeight ?? 1123 * scale,
    };

    const withEntrance = (
      style: React.CSSProperties,
      obj: EditorObject,
      index: number,
      extra?: {
        transformOrigin?: React.CSSProperties['transformOrigin'];
        rotationDeg?: number;
      },
    ) =>
      mergeEntranceAnimationStyle(style, obj, scale, index, phase, {
        ...extra,
        flyLayoutPx,
      });

    const renderObjectContent = (
      obj: EditorObject,
      index: number,
      options: RenderObjectOptions = {},
    ) => {
      const applyStyle = (
        style: React.CSSProperties,
        styleObj: EditorObject,
        styleIndex: number,
        extra?: {
          transformOrigin?: React.CSSProperties['transformOrigin'];
          rotationDeg?: number;
        },
      ) =>
        options.skipEntrance
          ? style
          : withEntrance(style, styleObj, styleIndex, extra);

      const actualStrokeWidth = obj.strokeWidth ?? 0;
      const hasStroke = actualStrokeWidth > 0;
      const baseStyle: React.CSSProperties = {
        position: 'absolute',
        left: `${(obj.x || 0) * scale}px`,
        top: `${(obj.y || 0) * scale}px`,
        // editor opacity is stored as 0-100, CSS expects 0-1
        opacity: (obj.opacity ?? 100) / 100,
        transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
        // Konva default transform origin is top-left for Rect/Text/Image/etc.
        transformOrigin: 'top left',
      };

      if (obj.visible === false) {
        return null;
      }

      const objectFillStyle: React.CSSProperties =
        obj.fillType === 'gradient' && obj.gradient
          ? { backgroundImage: toCssLinearGradient(obj.gradient) }
          : { backgroundColor: obj.fill || 'transparent' };
      const shapeBlurStyle = getCssBlurStyle(obj.blur, scale);

      switch (obj.type) {
        case 'text': {
          // برای text، opacity در رنگ اعمال می‌شود، پس باید opacity را از baseStyle حذف کنیم
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          const { opacity, transform: _baseTransform, ...textBaseStyle } = baseStyle;
          const lineHeight = obj.lineHeight ?? 1.2;
          const wrapped = usesWrappedLayout(obj.text, obj.height, obj.fontSize || 24, lineHeight);
          const rotation = obj.rotation || 0;
          const fontSize = obj.fontSize || 24;
          const measureOpts = {
            fontSize,
            fontFamily: obj.fontFamily,
            fontWeight: obj.fontWeight,
            letterSpacing: obj.letterSpacing ?? 0,
            lineHeight,
          };
          const textLayout = getViewerTextLayout({
            x: obj.x || 0,
            width: obj.width,
            text: obj.text,
            textAlign: obj.textAlign,
            rotation,
            scale,
            measureOpts,
            wrapped,
          });
          const maxWidthPx = Math.ceil(
            resolveTextMaxWidth({
              anchorX: (obj.x || 0) * scale,
              pageWidth: pageWidth ?? 794 * scale,
              defaultMaxWidth: (obj.textMaxWidth ?? DEFAULT_TEXT_MAX_WIDTH_PX) * scale,
            }),
          );
          const wrappedWidthPx =
            obj.width !== undefined ? Math.ceil(obj.width * scale) : undefined;
          const wrapByMaxWidth =
            (wrapped ? wrappedWidthPx : textLayout.widthPx) !== undefined &&
            (wrapped ? wrappedWidthPx : textLayout.widthPx)! > maxWidthPx;
          const finalWrapped = wrapped || wrapByMaxWidth;
          const resolvedWidthPx = finalWrapped
            ? (wrappedWidthPx !== undefined
              ? Math.min(wrappedWidthPx, maxWidthPx)
              : (textLayout.widthPx !== undefined
                ? Math.min(textLayout.widthPx, maxWidthPx)
                : undefined))
            : textLayout.widthPx;
          const anchorRightPx = Math.round((obj.x || 0) * scale);
          const resolvedLeftPx =
            resolvedWidthPx !== undefined
              ? anchorRightPx - resolvedWidthPx
              : textLayout.leftPx;
          return (
            <div
              key={obj.id || index}
              style={applyStyle(
                {
                  ...textBaseStyle,
                  left: `${resolvedLeftPx}px`,
                  width: resolvedWidthPx !== undefined ? `${resolvedWidthPx}px` : 'max-content',
                  fontSize: `${fontSize * scale}px`,
                  fontFamily: getFontFamily(obj.fontFamily),
                  fontWeight: getCssFontWeight(obj.fontWeight),
                  lineHeight,
                  textAlign: textLayout.textAlign,
                  direction: 'rtl',
                  color: getColorWithOpacity(obj.fill, obj.opacity),
                  whiteSpace: finalWrapped ? 'pre-wrap' : 'nowrap',
                  overflowWrap: finalWrapped ? 'break-word' : 'normal',
                  letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined,
                  boxSizing: 'content-box',
                  zIndex: index,
                  transform: textLayout.transform,
                  transformOrigin: textLayout.transformOrigin,
                },
                obj,
                index,
                { rotationDeg: rotation, transformOrigin: textLayout.transformOrigin },
              )}
            >
              {obj.text || ''}
            </div>
          );
        }

        case 'image':
        case 'sticker':
        case 'document':
          return (
            <img
              key={obj.id || index}
              src={obj.imageUrl || ''}
              alt=""
              style={applyStyle(
                getRasterObjectStyle(obj, scale, baseStyle, index),
                obj,
                index,
              )}
            />
          );

        case 'video': {
          const videoBorderRadius = getCssBorderRadius(obj.borderRadius, scale);
          const videoStyle: React.CSSProperties = {
            ...baseStyle,
            width: obj.width ? `${obj.width * scale}px` : 'auto',
            height: obj.height ? `${obj.height * scale}px` : 'auto',
            objectFit: 'contain',
            zIndex: index,
            ...(videoBorderRadius ? { borderRadius: videoBorderRadius, overflow: 'hidden' } : {}),
          };

          if (options.staticMedia) {
            return (
              <video
                key={obj.id || index}
                src={obj.videoUrl || ''}
                muted
                playsInline
                preload="metadata"
                style={applyStyle(
                  {
                    ...videoStyle,
                    pointerEvents: 'none',
                  },
                  obj,
                  index,
                )}
                onLoadedMetadata={(e) => {
                  const video = e.currentTarget;
                  if (video.currentTime === 0) {
                    video.currentTime = 0.1;
                  }
                }}
              />
            );
          }
          return (
            <video
              key={obj.id || index}
              src={obj.videoUrl || ''}
              controls
              style={applyStyle(
                {
                  ...videoStyle,
                  pointerEvents: 'auto',
                },
                obj,
                index,
              )}
              onClick={(e) => {
                e.stopPropagation();
              }}
              onMouseDown={(e) => {
                e.stopPropagation();
              }}
              onTouchStart={(e) => {
                e.stopPropagation();
              }}
            />
          );
        }

        case 'audio': {
          const audioBorderRadius = getCssBorderRadius(obj.borderRadius ?? 8, scale);
          const audioBaseStyle: React.CSSProperties = {
            ...baseStyle,
            width: obj.width ? `${obj.width * scale}px` : `${320 * scale}px`,
            height: obj.height ? `${obj.height * scale}px` : `${56 * scale}px`,
            zIndex: index,
            ...(audioBorderRadius ? { borderRadius: audioBorderRadius, overflow: 'hidden' } : {}),
          };

          if (options.staticMedia) {
            return (
              <div
                key={obj.id || index}
                style={applyStyle(
                  {
                    ...audioBaseStyle,
                    backgroundColor: '#f3f4f6',
                    pointerEvents: 'none',
                  },
                  obj,
                  index,
                )}
              />
            );
          }
          return (
            <audio
              key={obj.id || index}
              src={obj.audioUrl || ''}
              controls
              preload="metadata"
              style={applyStyle(
                {
                  ...audioBaseStyle,
                  pointerEvents: 'auto',
                },
                obj,
                index,
              )}
              onClick={(e) => {
                e.stopPropagation();
              }}
              onMouseDown={(e) => {
                e.stopPropagation();
              }}
              onTouchStart={(e) => {
                e.stopPropagation();
              }}
            />
          );
        }

        case 'link': {
          const isInternalLink = obj.linkUrl?.startsWith('page://');
          const linkStyle = {
            ...baseStyle,
            width: obj.width ? `${obj.width * scale}px` : 'auto',
            height: obj.height ? `${obj.height * scale}px` : 'auto',
            color: obj.fill || '#3b82f6',
            fontSize: `${(obj.fontSize || 24) * scale}px`,
            fontFamily: getFontFamily(obj.fontFamily),
            fontWeight: getCssFontWeight(obj.fontWeight),
            textDecoration: 'underline',
            display: 'flex',
            alignItems: 'center',
            whiteSpace: 'pre-wrap' as const,
            cursor: 'pointer',
          };

          if (isInternalLink) {
            // لینک داخلی - از button با onClick استفاده می‌کنیم
            return (
              <button
                key={obj.id || index}
                type="button"
                style={applyStyle(
                  {
                    ...linkStyle,
                    zIndex: 9999,
                    pointerEvents: 'auto',
                    border: 'none',
                    background: 'none',
                    padding: 0,
                    cursor: 'pointer',
                  },
                  obj,
                  index,
                )}
                onClick={(e) => {
                  e.preventDefault();
                  e.stopPropagation();
                  if (obj.linkUrl && onLinkClick) {
                    onLinkClick(obj.linkUrl);
                  }
                }}
                onMouseDown={(e) => {
                  e.stopPropagation();
                }}
                onTouchStart={(e) => {
                  e.stopPropagation();
                }}
              >
                {obj.text || obj.linkUrl || ''}
              </button>
            );
          } else {
            // لینک خارجی - از <a> با target="_blank" استفاده می‌کنیم
            return (
              <a
                key={obj.id || index}
                href={obj.linkUrl || '#'}
                target="_blank"
                rel="noopener noreferrer"
                style={applyStyle(
                  {
                    ...linkStyle,
                    zIndex: 9999,
                    pointerEvents: 'auto',
                  },
                  obj,
                  index,
                )}
                onClick={(e) => {
                  e.stopPropagation();
                }}
                onMouseDown={(e) => {
                  e.stopPropagation();
                }}
              >
                {obj.text || obj.linkUrl || ''}
              </a>
            );
          }
        }

        case 'rectangle': {
          if (obj.shapeType === 'circle') {
            // در editor، circle با مرکز در x, y رندر می‌شود (radius = width/2)
            // در dataTransformer از گوشه بالا-چپ به مرکز تبدیل شده است
            const radius = ((obj.width || 100) / 2) * scale;
            const centerX = (obj.x || 0) * scale;
            const centerY = (obj.y || 0) * scale;

            return (
              <div
                key={obj.id || index}
                style={applyStyle(
                  {
                    position: 'absolute',
                    left: `${centerX - radius}px`,
                    top: `${centerY - radius}px`,
                    width: `${radius * 2}px`,
                    height: `${radius * 2}px`,
                    borderRadius: '50%',
                    ...objectFillStyle,
                    ...shapeBlurStyle,
                    border: hasStroke && obj.stroke
                      ? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
                      : 'none',
                    boxSizing: 'border-box',
                    opacity: (obj.opacity ?? 100) / 100,
                    transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
                    transformOrigin: 'center center',
                    zIndex: index, // برای حفظ ترتیب رندر
                  },
                  obj,
                  index,
                  { transformOrigin: 'center center' },
                )}
              />
            );
          }

          if (obj.shapeType === 'triangle') {
            // در editor، triangle با مرکز در x + width/2, y + height/2 رندر می‌شود
            // RegularPolygon در Konva یک مثلث متساوی‌الاضلاع است با radius از مرکز تا رأس
            // radius باید بر اساس ابعاد اصلی (قبل از scale) محاسبه شود
            const baseWidth = obj.width || 100;
            const baseHeight = obj.height || 100;
            const baseRadius = Math.min(baseWidth, baseHeight) / 2;
            const radius = baseRadius * scale;

            // محاسبه مرکز مثلث (مثل editor)
            const centerX = ((obj.x || 0) + baseWidth / 2) * scale;
            const centerY = ((obj.y || 0) + baseHeight / 2) * scale;

            // برای مثلث متساوی‌الاضلاع، size = radius * 2
            const size = radius * 2;
            const halfSize = size / 2;

            // نقاط مثلث متساوی‌الاضلاع: رأس بالا (50%, 0%), پایین چپ (0%, 100%), پایین راست (100%, 100%)
            // اما برای مثلث متساوی‌الاضلاع واقعی، باید ارتفاع را محاسبه کنیم
            // ارتفاع مثلث متساوی‌الاضلاع = size * sqrt(3) / 2 ≈ size * 0.866
            const triangleHeight = size * 0.8660254; // sqrt(3) / 2
            const topY = (size - triangleHeight) / 2;
            const bottomY = size - topY;
            const halfWidth = triangleHeight * 0.5773503; // tan(30°) = 1/sqrt(3)

            const topX = halfSize;
            const bottomLeftX = halfSize - halfWidth;
            const bottomRightX = halfSize + halfWidth;

            const strokeWidth = hasStroke && obj.stroke ? actualStrokeWidth * scale : 0;
            const fillColor = obj.fill || '#000000';
            const strokeColor = obj.stroke || 'transparent';
            const gradientId = `triangle-grad-${obj.id}-${index}${idSuffix}`;
            const triangleGradientEndpoints =
              obj.fillType === 'gradient' && obj.gradient
                ? getSvgGradientEndpoints(
                    obj.gradient,
                    baseWidth * scale,
                    baseHeight * scale,
                    'centered',
                    { x: halfSize, y: halfSize },
                  )
                : null;

            return (
              <svg
                key={obj.id || index}
                style={applyStyle(
                  {
                    position: 'absolute',
                    left: `${centerX - radius}px`,
                    top: `${centerY - radius}px`,
                    width: `${size}px`,
                    height: `${size}px`,
                    opacity: (obj.opacity ?? 100) / 100,
                    ...shapeBlurStyle,
                    transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
                    transformOrigin: 'center center',
                    zIndex: index,
                    overflow: 'visible',
                  },
                  obj,
                  index,
                  { transformOrigin: 'center center' },
                )}
                viewBox={`0 0 ${size} ${size}`}
              >
                {triangleGradientEndpoints ? (
                  <defs>
                    <linearGradient
                      id={gradientId}
                      gradientUnits="userSpaceOnUse"
                      x1={triangleGradientEndpoints.x1}
                      y1={triangleGradientEndpoints.y1}
                      x2={triangleGradientEndpoints.x2}
                      y2={triangleGradientEndpoints.y2}
                    >
                      <stop offset="0%" stopColor={obj.gradient!.from} />
                      <stop offset="100%" stopColor={obj.gradient!.to} />
                    </linearGradient>
                  </defs>
                ) : null}
                <polygon
                  points={`${topX},${topY} ${bottomLeftX},${bottomY} ${bottomRightX},${bottomY}`}
                  fill={triangleGradientEndpoints ? `url(#${gradientId})` : fillColor}
                  stroke={strokeColor}
                  strokeWidth={strokeWidth}
                />
              </svg>
            );
          }

          if (obj.shapeType === 'abstract') {
            // در editor، abstract (star) با مرکز در x + width/2, y + height/2 رندر می‌شود
            const width = (obj.width || 100) * scale;
            const height = (obj.height || 100) * scale;
            const baseRadius = Math.min(width, height) / 2;
            const abstractScale = 0.85;
            const radius = baseRadius * abstractScale;
            const centerX = (obj.x || 0) * scale + width / 2;
            const centerY = (obj.y || 0) * scale + height / 2;

            return (
              <div
                key={obj.id || index}
                style={applyStyle(
                  {
                    position: 'absolute',
                    left: `${centerX - radius}px`,
                    top: `${centerY - radius}px`,
                    width: `${radius * 2}px`,
                    height: `${radius * 2}px`,
                    clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
                    ...objectFillStyle,
                    ...shapeBlurStyle,
                    border: obj.stroke
                      ? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}`
                      : 'none',
                    boxSizing: 'border-box',
                    opacity: (obj.opacity ?? 100) / 100,
                    transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
                    transformOrigin: 'center center',
                  },
                  obj,
                  index,
                  { transformOrigin: 'center center' },
                )}
              />
            );
          }

          // Rectangle عادی
          const shapeStyle: React.CSSProperties = {
            ...baseStyle,
            width: `${(obj.width || 100) * scale}px`,
            height: `${(obj.height || 100) * scale}px`,
            ...objectFillStyle,
            ...shapeBlurStyle,
            borderRadius: `${Math.max(0, (obj.borderRadius || 0) * scale)}px`,
            border: hasStroke && obj.stroke
              ? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
              : 'none',
            boxSizing: 'border-box',
            zIndex: index, // برای حفظ ترتیب رندر
          };

          return <div key={obj.id || index} style={applyStyle(shapeStyle, obj, index)} />;
        }

        case 'line': {
          // در EditorObject، width و height به عنوان مختصات انتهایی استفاده می‌شوند
          // در Konva Line، points به صورت [0, 0, endX - startX, endY - startY] است
          const startX = (obj.x || 0) * scale;
          const startY = (obj.y || 0) * scale;
          // width و height مستقیماً مختصات نقطه پایان هستند
          const endX = (obj.width ?? obj.x ?? 0) * scale;
          const endY = (obj.height ?? obj.y ?? 0) * scale;
          const dx = endX - startX;
          const dy = endY - startY;
          const lineLength = Math.sqrt(dx * dx + dy * dy);
          const lineAngle = Math.atan2(dy, dx) * (180 / Math.PI);

          return (
            <div
              key={obj.id || index}
              style={applyStyle(
                {
                  position: 'absolute',
                  left: `${startX}px`,
                  top: `${startY}px`,
                  width: `${lineLength}px`,
                  height: `${(obj.strokeWidth || 2) * scale}px`,
                  backgroundColor: obj.stroke || '#000000',
                  opacity: (obj.opacity ?? 100) / 100,
                  transform: `rotate(${lineAngle}deg)`,
                  transformOrigin: 'left center',
                },
                obj,
                index,
                { rotationDeg: lineAngle, transformOrigin: 'left center' },
              )}
            />
          );
        }

        case 'arrow': {
          // در EditorObject، width و height به عنوان مختصات انتهایی استفاده می‌شوند
          // در Konva Arrow، points به صورت [0, 0, endX - startX, endY - startY] است
          const startX = (obj.x || 0) * scale;
          const startY = (obj.y || 0) * scale;
          // width و height مستقیماً مختصات نقطه پایان هستند
          const endX = (obj.width ?? obj.x ?? 0) * scale;
          const endY = (obj.height ?? obj.y ?? 0) * scale;
          const dx = endX - startX;
          const dy = endY - startY;
          const arrowLength = Math.sqrt(dx * dx + dy * dy);
          const arrowAngle = Math.atan2(dy, dx) * (180 / Math.PI);
          const strokeWidth = (obj.strokeWidth || 4) * scale;

          return (
            <div
              key={obj.id || index}
              style={applyStyle(
                {
                  position: 'absolute',
                  left: `${startX}px`,
                  top: `${startY}px`,
                  width: `${arrowLength}px`,
                  height: `${strokeWidth}px`,
                  backgroundColor: obj.stroke || '#000000',
                  opacity: (obj.opacity ?? 100) / 100,
                  transform: `rotate(${arrowAngle}deg)`,
                  transformOrigin: 'left center',
                },
                obj,
                index,
                { rotationDeg: arrowAngle, transformOrigin: 'left center' },
              )}
            >
              <div
                style={{
                  position: 'absolute',
                  right: `-${strokeWidth * 2}px`,
                  top: '50%',
                  transform: 'translateY(-50%)',
                  width: 0,
                  height: 0,
                  borderTop: `${strokeWidth * 2}px solid transparent`,
                  borderBottom: `${strokeWidth * 2}px solid transparent`,
                  borderLeft: `${strokeWidth * 2}px solid ${obj.stroke || '#000000'}`,
                }}
              />
            </div>
          );
        }

        case 'custom-shape': {
          const rawPoints = obj.points ?? [];
          const isClosed = obj.closed ?? true;
          const minLength = isClosed ? 6 : 4;
          if (rawPoints.length < minLength) return null;

          const { minX, minY, width: bboxW, height: bboxH } =
            getCustomShapePointBounds(rawPoints);
          const width = bboxW * scale;
          const height = bboxH * scale;
          const pointsStr = Array.from(
            { length: rawPoints.length / 2 },
            (_, i) =>
              `${(rawPoints[i * 2] - minX) * scale},${(rawPoints[i * 2 + 1] - minY) * scale}`,
          ).join(' ');

          const strokeWidth = hasStroke && obj.stroke ? actualStrokeWidth * scale : 0;
          const fillColor = obj.fill || '#3b82f6';
          const strokeColor = obj.stroke || 'transparent';
          const gradientId = `custom-shape-grad-${obj.id}-${index}${idSuffix}`;
          const gradientEndpoints =
            obj.fillType === 'gradient' && obj.gradient
              ? getSvgGradientEndpoints(obj.gradient, width, height, 'rect')
              : null;

          return (
            <svg
              key={obj.id || index}
              style={applyStyle(
                {
                  position: 'absolute',
                  left: `${((obj.x || 0) + minX) * scale}px`,
                  top: `${((obj.y || 0) + minY) * scale}px`,
                  width: `${width}px`,
                  height: `${height}px`,
                  opacity: (obj.opacity ?? 100) / 100,
                  transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
                  transformOrigin: 'top left',
                  zIndex: index,
                  overflow: 'visible',
                },
                obj,
                index,
              )}
              viewBox={`0 0 ${width} ${height}`}
            >
              {gradientEndpoints ? (
                <defs>
                  <linearGradient
                    id={gradientId}
                    gradientUnits="userSpaceOnUse"
                    x1={gradientEndpoints.x1}
                    y1={gradientEndpoints.y1}
                    x2={gradientEndpoints.x2}
                    y2={gradientEndpoints.y2}
                  >
                    <stop offset="0%" stopColor={obj.gradient!.from} />
                    <stop offset="100%" stopColor={obj.gradient!.to} />
                  </linearGradient>
                </defs>
              ) : null}
              {isClosed ? (
                <polygon
                  points={pointsStr}
                  fill={gradientEndpoints ? `url(#${gradientId})` : fillColor}
                  stroke={strokeColor}
                  strokeWidth={strokeWidth}
                />
              ) : (
                <polyline
                  points={pointsStr}
                  fill="none"
                  stroke={strokeColor}
                  strokeWidth={strokeWidth}
                />
              )}
            </svg>
          );
        }

        case 'pencil': {
          const rawPoints = obj.points ?? [];
          if (rawPoints.length < 4) return null;

          const { minX, minY, width: bboxW, height: bboxH } =
            getCustomShapePointBounds(rawPoints);
          const width = bboxW * scale;
          const height = bboxH * scale;
          const pointsStr = Array.from(
            { length: rawPoints.length / 2 },
            (_, i) =>
              `${(rawPoints[i * 2] - minX) * scale},${(rawPoints[i * 2 + 1] - minY) * scale}`,
          ).join(' ');

          const strokeWidth = actualStrokeWidth * scale;
          const strokeColor = obj.stroke || '#000000';

          return (
            <svg
              key={obj.id || index}
              style={applyStyle(
                {
                  position: 'absolute',
                  left: `${((obj.x || 0) + minX) * scale}px`,
                  top: `${((obj.y || 0) + minY) * scale}px`,
                  width: `${width}px`,
                  height: `${height}px`,
                  opacity: (obj.opacity ?? 100) / 100,
                  transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
                  transformOrigin: 'top left',
                  zIndex: index,
                  overflow: 'visible',
                },
                obj,
                index,
              )}
              viewBox={`0 0 ${width} ${height}`}
            >
              <polyline
                points={pointsStr}
                fill="none"
                stroke={strokeColor}
                strokeWidth={strokeWidth}
                strokeLinecap="round"
                strokeLinejoin="round"
              />
            </svg>
          );
        }

        case 'grid': {
          if (!obj.tableData) return null;

          const { rows, cols, cells, cellWidth, cellHeight, stroke, strokeWidth } =
            obj.tableData;

          return (
            <div
              key={obj.id || index}
              style={applyStyle(
                {
                  ...baseStyle,
                  display: 'grid',
                  gridTemplateRows: `repeat(${rows}, ${cellHeight * scale}px)`,
                  gridTemplateColumns: `repeat(${cols}, ${cellWidth * scale}px)`,
                  border: stroke ? `${(strokeWidth || 1) * scale}px solid ${stroke}` : 'none',
                },
                obj,
                index,
              )}
            >
              {Array.from({ length: rows }).map((_, rowIndex) =>
                Array.from({ length: cols }).map((_, colIndex) => {
                  const cellId = `cell-${rowIndex}-${colIndex}`;
                  const cell = cells[cellId];

                  return (
                    <div
                      key={cellId}
                      style={{
                        border: stroke ? `${(strokeWidth || 1) * scale}px solid ${stroke}` : `${1 * scale}px solid #ccc`,
                        backgroundColor: cell?.background || '#ffffff',
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'center',
                        padding: `${4 * scale}px`,
                        fontSize: `${14 * scale}px`,
                      }}
                    >
                      {cell?.text || ''}
                    </div>
                  );
                })
              )}
            </div>
          );
        }

        default:
          return null;
      }
    };

    const renderObject = (obj: EditorObject, index: number, allElements: EditorObject[]) => {
      if (obj.isMask) return null;

      const maskShape = obj.maskId
        ? allElements.find((m) => m.id === obj.maskId)
        : null;

      if (!maskShape) {
        return renderObjectContent(obj, index, { staticMedia });
      }

      const layout = getMaskedLayout(obj, maskShape, scale);
      const unionLeft = layout.left / scale;
      const unionTop = layout.top / scale;

      const content = renderObjectContent(
        { ...obj, x: (obj.x || 0) - unionLeft, y: (obj.y || 0) - unionTop },
        index,
        { skipEntrance: true, staticMedia },
      );

      if (!content) return null;

      const wrapperStyle: React.CSSProperties = {
        position: 'absolute',
        left: `${layout.left}px`,
        top: `${layout.top}px`,
        width: `${layout.width}px`,
        height: `${layout.height}px`,
        overflow: 'visible',
        zIndex: index,
        ...getMaskImageStyle(obj, maskShape, scale, layout),
      };

      return (
        <div
          key={`masked-${obj.id || index}`}
          style={withEntrance(wrapperStyle, obj, index)}
        >
          {content}
        </div>
      );
    };

    const width = pageWidth ?? 794 * scale;
    const height = pageHeight ?? 1123 * scale;

    const effectiveBackgroundType = backgroundType ?? page.backgroundType ?? 'color';
    const effectiveBackgroundColor = backgroundColor ?? page.backgroundColor ?? '#ffffff';
    const effectiveBackgroundGradient = backgroundGradient ?? page.backgroundGradient;
    const effectiveBackgroundImageUrl = backgroundImageUrl ?? page.backgroundImageUrl ?? '';
    const effectiveBackgroundVideoUrl = backgroundVideoUrl ?? page.backgroundVideoUrl ?? '';

    const bgStyle: React.CSSProperties =
      effectiveBackgroundType === 'image' && effectiveBackgroundImageUrl
        ? {
            backgroundImage: `url(${effectiveBackgroundImageUrl})`,
            backgroundSize: 'cover',
            backgroundPosition: 'center',
            backgroundRepeat: 'no-repeat',
          }
        : effectiveBackgroundType === 'gradient' && effectiveBackgroundGradient
          ? {
              backgroundImage: toCssLinearGradient(effectiveBackgroundGradient),
            }
        : { backgroundColor: effectiveBackgroundColor };

    return (
      <div
        ref={ref}
        dir="rtl"
        className="page"
        data-page-id={page.id}
        data-density={useHardPage ? 'hard' : 'soft'}
        style={{
          position: 'relative',
          overflow: 'hidden',
          boxSizing: 'border-box',
          width: `${width}px`,
          height: `${height}px`,
          zIndex: 1,
          ...(useHardPage
            ? { backgroundColor: effectiveBackgroundColor, ...bgStyle }
            : {}),
        }}
      >
        {!useHardPage && (
          <>
            <div
              aria-hidden
              style={{
                position: 'absolute',
                inset: 0,
                ...bgStyle,
                pointerEvents: 'none',
                zIndex: 0,
              }}
            />
            {effectiveBackgroundType === 'video' && effectiveBackgroundVideoUrl && (
              <video
                aria-hidden
                src={effectiveBackgroundVideoUrl}
                autoPlay
                loop
                muted
                playsInline
                preload="metadata"
                style={{
                  position: 'absolute',
                  inset: 0,
                  width: '100%',
                  height: '100%',
                  objectFit: 'cover',
                  pointerEvents: 'none',
                  zIndex: 0,
                }}
              />
            )}
          </>
        )}
        <div style={{ position: 'absolute', inset: 0, zIndex: 1 }}>
          {page.elements.map((element, index) => renderObject(element, index, page.elements))}
        </div>
        {useHardPage && showPaperShadow && <div aria-hidden className="page-paper-shadow" />}
      </div>
    );
  }),
);

BookPage.displayName = 'BookPage';

export default BookPage;

