import { forwardRef } from 'react';
import { type PageData } from '../types';
import type { EditorObject } from '@/pages/editor/store/editorStore';
import { toCssLinearGradient } from '@/pages/editor/utils/gradient';
import { getFontFamily } from '@/pages/editor/utils/fontFamily';
import {
  getCssFontWeight,
  getViewerTextLayout,
  usesWrappedLayout,
} from '@/pages/editor/utils/textStyle';
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';

type RenderObjectOptions = {
  skipEntrance?: boolean;
};

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

/**
 * کامپوننت خالص برای نمایش محتوای یک صفحه کتاب
 * تمام المنت‌ها با absolute positioning رندر می‌شوند
 * از HTML استفاده می‌شود (نه Canvas) برای سازگاری با Konva در آینده
 * این کامپوننت به عنوان child برای HTMLFlipBook استفاده می‌شود
 * نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
 */
const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
  ({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl, entrancePhase = 'idle', disableEntranceAnimations = false, showPaperShadow = true, useHardPage = false }, 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' };

      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,
          });
          return (
            <div
              key={obj.id || index}
              style={applyStyle(
                {
                  ...textBaseStyle,
                  left: `${textLayout.leftPx}px`,
                  width: textLayout.widthPx !== undefined ? `${textLayout.widthPx}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: wrapped ? 'pre-wrap' : 'nowrap',
                  overflowWrap: wrapped ? 'break-word' : 'normal',
                  letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined,
                  boxSizing: 'content-box',
                  transform: textLayout.transform,
                  transformOrigin: textLayout.transformOrigin,
                },
                obj,
                index,
                { rotationDeg: rotation, transformOrigin: textLayout.transformOrigin },
              )}
            >
              {obj.text || ''}
            </div>
          );
        }

        case 'image':
        case 'sticker':
          return (
            <img
              key={obj.id || index}
              src={obj.imageUrl || ''}
              alt=""
              style={applyStyle(
                {
                  ...baseStyle,
                  width: obj.width ? `${obj.width * scale}px` : 'auto',
                  height: obj.height ? `${obj.height * scale}px` : 'auto',
                  objectFit: 'fill', // مشابه Konva که image را با width و height مشخص شده رندر می‌کند
                  zIndex: index, // برای حفظ ترتیب رندر
                },
                obj,
                index,
              )}
            />
          );

        case 'video':
          return (
            <video
              key={obj.id || index}
              src={obj.videoUrl || ''}
              controls
              style={applyStyle(
                {
                  ...baseStyle,
                  width: obj.width ? `${obj.width * scale}px` : 'auto',
                  height: obj.height ? `${obj.height * scale}px` : 'auto',
                  objectFit: 'contain',
                  zIndex: index,
                  pointerEvents: 'auto',
                },
                obj,
                index,
              )}
              onClick={(e) => {
                e.stopPropagation();
              }}
              onMouseDown={(e) => {
                e.stopPropagation();
              }}
              onTouchStart={(e) => {
                e.stopPropagation();
              }}
            />
          );

        case 'audio':
          return (
            <audio
              key={obj.id || index}
              src={obj.audioUrl || ''}
              controls
              preload="metadata"
              style={applyStyle(
                {
                  ...baseStyle,
                  width: obj.width ? `${obj.width * scale}px` : `${320 * scale}px`,
                  height: obj.height ? `${obj.height * scale}px` : `${56 * scale}px`,
                  zIndex: index,
                  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,
                    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}`;

            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,
                    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}`}
              >
                {obj.fillType === 'gradient' && obj.gradient ? (
                  <defs>
                    <linearGradient
                      id={gradientId}
                      gradientUnits="userSpaceOnUse"
                      x1="0"
                      y1="0"
                      x2={size}
                      y2={size}
                      gradientTransform={`rotate(${obj.gradient.angle}, ${size / 2}, ${size / 2})`}
                    >
                      <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={obj.fillType === 'gradient' && obj.gradient ? `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,
                    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,
            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 width = (obj.width || 100) * scale;
          const height = (obj.height || 100) * scale;
          const pointsStr = Array.from(
            { length: rawPoints.length / 2 },
            (_, i) => `${rawPoints[i * 2] * scale},${rawPoints[i * 2 + 1] * 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}`;

          return (
            <svg
              key={obj.id || index}
              style={applyStyle(
                {
                  position: 'absolute',
                  left: `${(obj.x || 0) * scale}px`,
                  top: `${(obj.y || 0) * 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}`}
            >
              {obj.fillType === 'gradient' && obj.gradient ? (
                <defs>
                  <linearGradient
                    id={gradientId}
                    gradientUnits="userSpaceOnUse"
                    x1="0"
                    y1="0"
                    x2={width}
                    y2={height}
                    gradientTransform={`rotate(${obj.gradient.angle}, ${width / 2}, ${height / 2})`}
                  >
                    <stop offset="0%" stopColor={obj.gradient.from} />
                    <stop offset="100%" stopColor={obj.gradient.to} />
                  </linearGradient>
                </defs>
              ) : null}
              {isClosed ? (
                <polygon
                  points={pointsStr}
                  fill={obj.fillType === 'gradient' && obj.gradient ? `url(#${gradientId})` : fillColor}
                  stroke={strokeColor}
                  strokeWidth={strokeWidth}
                />
              ) : (
                <polyline
                  points={pointsStr}
                  fill="none"
                  stroke={strokeColor}
                  strokeWidth={strokeWidth}
                />
              )}
            </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);
      }

      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 },
      );

      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 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-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,
            }}
          />
        )}
        <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;

