import { mdiContentSave, mdiExitToApp, mdiPlus } from '@mdi/js';
import axios from 'axios';
import Head from 'next/head';
import { useRouter } from 'next/router';
import React, {
  ReactElement,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import BaseButton from '../components/BaseButton';
import CanvasBackground from '../components/Constructor/CanvasBackground';
import ConstructorControlsPanel from '../components/Constructor/ConstructorControlsPanel';
import ConstructorMenu from '../components/Constructor/ConstructorMenu';
import TransitionPreviewOverlay from '../components/Constructor/TransitionPreviewOverlay';
import CanvasElementComponent from '../components/Constructor/CanvasElement';
import GalleryCarouselOverlay from '../components/UiElements/GalleryCarouselOverlay';
import ElementEditorPanel from '../components/Constructor/ElementEditorPanel';
import { BackdropPortalProvider } from '../components/BackdropPortal';
import { getPageTitle } from '../config';
import LayoutAuthenticated from '../layouts/Authenticated';
import { usePreloadOrchestrator } from '../hooks/usePreloadOrchestrator';
import { usePageSwitch } from '../hooks/usePageSwitch';
import { usePageNavigation } from '../hooks/usePageNavigation';
import { useTransitionPlayback } from '../hooks/useTransitionPlayback';
import { useBackgroundTransition } from '../hooks/useBackgroundTransition';
import { useBackgroundUrls } from '../hooks/useBackgroundUrls';
import { logger } from '../lib/logger';
import { resolveAssetPlaybackUrl } from '../lib/assetUrl';
import { parseJsonObject } from '../lib/parseJson';
import {
  resolveNavigationTarget,
  hasPlayableTransition,
  getNavigationDirection,
  isBackNavigation,
} from '../lib/navigationHelpers';
import {
  mergeElementWithDefaults,
  createLocalId,
  normalizeAppearDelaySec,
  normalizeAppearDurationSec,
  ELEMENT_TYPE_LABELS,
  getNavigationButtonKind,
  isNavigationElementType,
  isTooltipElementType,
  isDescriptionElementType,
  isMediaElementType,
  isVideoPlayerElementType,
  clamp,
} from '../lib/elementDefaults';
import type {
  CanvasElementType,
  CanvasElement,
  ConstructorSchema,
  ConstructorAsset as ProjectAsset,
  EditorMenuItem,
  GalleryCard,
  GalleryInfoSpan,
  CarouselSlide,
} from '../types/constructor';
import type { TourPage } from '../types/entities';

// Constructor-specific hooks
import {
  useCanvasElapsedTime,
  isElementVisibleAtTime,
} from '../hooks/useCanvasElapsedTime';
import {
  useMediaDurationProbe,
  buildDurationProbeTargets,
} from '../hooks/useMediaDurationProbe';
import { useIconPreload } from '../hooks/useIconPreload';
import { useOutsideClick } from '../hooks/useOutsideClick';
import { useDraggable } from '../hooks/useDraggable';
import { useCanvasElementDrag } from '../hooks/useCanvasElementDrag';
import { useTransitionPreview } from '../hooks/useTransitionPreview';
import { useConstructorPageActions } from '../hooks/useConstructorPageActions';
import { useConstructorElements } from '../hooks/useConstructorElements';
import { usePageBackground } from '../hooks/usePageBackground';
import { useConstructorData } from '../hooks/useConstructorData';
import { useAssetOptions } from '../hooks/useAssetOptions';
import { useTransitionCreation } from '../hooks/useTransitionCreation';
import {
  ConstructorProvider,
  type ConstructorContextValue,
  type NavigationElementType,
} from '../context/ConstructorContext';
import { useCanvasScale } from '../hooks/useCanvasScale';
import { CANVAS_CONFIG } from '../config/canvas.config';

// Constructor helpers (extracted utilities)
import {
  getAssetLabel,
  getAssetSourceValue,
  isBackgroundImageAsset,
} from '../lib/constructorHelpers';

// TourPage type is imported from '../types/entities'
// NavigationElementType is imported from '../context/ConstructorContext'

type ConstructorPageProps = {
  mode?: 'constructor' | 'element_edit';
};

type ConstructorInteractionMode = 'edit' | 'interact';

// Use ELEMENT_TYPE_LABELS from elementDefaults for label lookup
const labelByType = ELEMENT_TYPE_LABELS;

const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
  const router = useRouter();
  const canvasRef = useRef<HTMLDivElement>(null);
  const elementEditorRef = useRef<HTMLDivElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);
  const [isAuthReady, setIsAuthReady] = useState(false);
  const isElementEditMode = mode === 'element_edit';

  const projectId = useMemo(() => {
    const value = router.query.projectId;
    if (Array.isArray(value)) return value[0] || '';
    return String(value || '');
  }, [router.query.projectId]);
  const pageElementsListHref = useMemo(() => {
    if (!projectId)
      return '/project-element-defaults/project-element-defaults-list';
    return `/project-element-defaults/project-element-defaults-list?projectId=${encodeURIComponent(projectId)}`;
  }, [projectId]);

  const pageIdFromRoute = useMemo(() => {
    const value = router.query.pageId;
    if (Array.isArray(value)) return value[0] || '';
    return String(value || '');
  }, [router.query.pageId]);
  const elementIdFromRoute = useMemo(() => {
    const value = router.query.elementId;
    if (Array.isArray(value)) return value[0] || '';
    return String(value || '');
  }, [router.query.elementId]);

  // Use React Query for data fetching (replaces manual loadData)
  const {
    project,
    pages,
    pageLinks,
    allPagesPreloadElements,
    assets,
    uiElementDefaultsByType,
    projectName,
    isLoading: isDataLoading,
    isError: isDataError,
    error: dataError,
    refetch: refetchData,
  } = useConstructorData({
    projectId,
    isAuthReady,
  });

  // Canvas scale for responsive UI elements and letterbox mode
  const { cssVars: canvasCssVars, letterboxStyles } = useCanvasScale({
    designWidth: project?.design_width,
    designHeight: project?.design_height,
  });

  // Page navigation with history tracking via shared hook
  const {
    currentPageId: activePageId,
    pageHistory,
    applyPageSelection,
    getNavigationContext,
    setCurrentPageId: setActivePageId,
  } = usePageNavigation({
    pages,
    trackHistory: true,
  });

  // Consolidated page background state (replaces 8 separate useState hooks)
  const {
    background: pageBackground,
    setBackground: setPageBackground,
    updateFromPage: updateBackgroundFromPage,
    setImageUrl: setBackgroundImageUrl,
    setVideoUrl: setBackgroundVideoUrl,
    setAudioUrl: setBackgroundAudioUrl,
    setVideoSettings: setBackgroundVideoSettings,
    // Legacy compatibility values for components that expect flat props
    backgroundImageUrl,
    backgroundVideoUrl,
    backgroundAudioUrl,
    backgroundVideoAutoplay,
    backgroundVideoLoop,
    backgroundVideoMuted,
    backgroundVideoStartTime,
    backgroundVideoEndTime,
  } = usePageBackground();

  const [selectedMenuItem, setSelectedMenuItem] =
    useState<EditorMenuItem>('none');
  // Transition preview state managed by useTransitionPreview hook (below)

  // Combined loading state: initial auth check + data loading
  const [isInitializing, setIsInitializing] = useState(true);
  const isLoading = isInitializing || isDataLoading;
  // isSaving, isSavingToStage, isCreatingPage are managed by useConstructorPageActions hook
  const [newTransitionName, setNewTransitionName] = useState('');
  const [newTransitionVideoUrl, setNewTransitionVideoUrl] = useState('');
  const [newTransitionSupportsReverse, setNewTransitionSupportsReverse] =
    useState(true);
  const [errorMessage, setErrorMessage] = useState('');
  const [successMessage, setSuccessMessage] = useState('');
  const [constructorInteractionMode, setConstructorInteractionMode] =
    useState<ConstructorInteractionMode>('edit');
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const [isEditorCollapsed, setIsEditorCollapsed] = useState(false);
  const [elementEditorTab, setElementEditorTab] = useState<
    'general' | 'css' | 'effects'
  >('general');
  const [activeGalleryCarousel, setActiveGalleryCarousel] = useState<{
    element: CanvasElement;
    initialIndex: number;
  } | null>(null);
  // Track background ready state for smooth video transition completion
  const [isBackgroundReady, setIsBackgroundReady] = useState(true);
  const [pendingTransitionComplete, setPendingTransitionComplete] =
    useState(false);

  const isConstructorEditMode = constructorInteractionMode === 'edit';
  const allowedNavigationTypes = useMemo<NavigationElementType[]>(() => {
    return ['navigation_next', 'navigation_prev'];
  }, []);

  // Element CRUD operations via useConstructorElements hook
  const {
    elements,
    setElements,
    selectedElementId,
    selectedElement,
    selectElement,
    clearSelection,
    addElement,
    updateSelectedElement,
    removeSelectedElement,
    galleryCards,
    galleryInfoSpans,
    carouselSlides,
    updateElementPosition,
    normalizeNavigationType,
  } = useConstructorElements({
    initialElements: [],
    elementDefaultsByType: uiElementDefaultsByType,
    allowedNavigationTypes,
    initialSelectedElementId: elementIdFromRoute,
    onElementSelected: useCallback(() => {
      setSelectedMenuItem('none');
    }, []),
    onSelectionCleared: useCallback(() => {
      setSelectedMenuItem('none');
    }, []),
    onElementAdded: useCallback(() => {
      setSuccessMessage('Element added. Drag it to set position.');
      setErrorMessage('');
    }, []),
    onElementRemoved: useCallback(() => {
      setSuccessMessage('Element removed.');
    }, []),
  });

  // Check if any element has full-width carousel mode enabled
  const hasFullWidthCarousel = useMemo(
    () => elements.some((el) => el.carouselFullWidth === true),
    [elements],
  );

  // Draggable panels using useDraggable hook
  const {
    position: constructorControlsPosition,
    onDragStart: onConstructorControlsDragStart,
  } = useDraggable({
    initialPosition: { x: 20, y: 20 },
    elementWidth: 460,
    elementHeight: 64,
  });

  const { position: menuPosition, onDragStart: onMenuDragStart } = useDraggable(
    {
      initialPosition: { x: 9999, y: 10 }, // Top right corner (x will be clamped)
      elementWidth: 240,
      elementHeight: 60,
    },
  );

  const { position: editorPosition, onDragStart: onElementEditorDragStart } =
    useDraggable({
      initialPosition: { x: 0, y: 72 },
      elementWidth: isEditorCollapsed ? 260 : 380,
      elementHeight: 60,
    });

  const transitionVideoRef = useRef<HTMLVideoElement | null>(null);
  const lastInitializedPageIdRef = useRef<string | null>(null);
  const didSetInitialCanvasFocus = useRef(false);
  const selectedElementIdRef = useRef<string>('');
  selectedElementIdRef.current = selectedElementId;

  const activePage = useMemo(
    () => pages.find((item) => item.id === activePageId) || null,
    [activePageId, pages],
  );

  // Transition preview state management
  const {
    preview: transitionPreview,
    pendingPageId: pendingNavigationPageId,
    openPreview: openTransitionPreviewForElement,
    openPreviewWithTarget,
    closePreview: closeTransitionPreview,
  } = useTransitionPreview({
    isNavigationElementType,
    onError: setErrorMessage,
  });

  // Canvas elapsed time for element visibility timing
  const { elapsedSec: canvasElapsedSec } = useCanvasElapsedTime({
    pageId: activePageId,
    enabled: !isLoading,
  });

  // Element dragging with percentage positioning
  const { onElementDragStart, cancelDrag: cancelElementDrag } =
    useCanvasElementDrag({
      canvasRef,
      onPositionChange: updateElementPosition,
      enabled: constructorInteractionMode === 'edit',
    });

  // Preload orchestrator for better DX when previewing pages
  // Preloads neighbor page assets and transition videos
  // Uses allPagesPreloadElements (extracted in loadData) for proper neighbor preloading
  const preloadOrchestrator = usePreloadOrchestrator({
    pages: pages.map((p) => ({
      id: p.id,
      background_image_url: p.background_image_url,
      background_video_url: p.background_video_url,
      background_audio_url: p.background_audio_url,
    })),
    pageLinks,
    elements: allPagesPreloadElements, // Use elements from ALL pages for proper neighbor preloading
    currentPageId: activePageId,
    enabled: !isLoading && !!activePageId,
    // maxNeighborDepth defaults to 1 - only preload immediate neighbors
  });

  // Page switch hook for smooth background transitions (uses blob URLs from preload cache)
  const pageSwitch = usePageSwitch({
    preloadCache: preloadOrchestrator
      ? {
          getReadyBlobUrl: preloadOrchestrator.getReadyBlobUrl,
          getCachedBlobUrl: preloadOrchestrator.getCachedBlobUrl,
          preloadedUrls: preloadOrchestrator.preloadedUrls,
        }
      : undefined,
  });
  // Destructure stable callback reference to avoid infinite loops in useEffect deps
  const pageSwitchToPage = pageSwitch.switchToPage;

  // Use shared background transition hook for direct navigation clearing and crossfade
  // Crossfade starts automatically when new background is ready
  const { isFadingIn } = useBackgroundTransition({
    pageSwitch,
    fadeIn: {
      hasActiveTransition: Boolean(transitionPreview),
    },
  });

  // Video transition overlay removal - instant (no fade) when background is ready
  // Uses double RAF to ensure browser has painted the new background before removing overlay
  useEffect(() => {
    if (pendingTransitionComplete && isBackgroundReady) {
      // Wait for paint cycle to complete before removing overlay
      // Double RAF ensures the new background is fully rendered
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          const video = transitionVideoRef.current;
          if (video) {
            video.removeAttribute('src');
            video.load();
          }
          closeTransitionPreview();
          setPendingTransitionComplete(false);
        });
      });
    }
  }, [pendingTransitionComplete, isBackgroundReady, closeTransitionPreview]);

  // Handle background ready state for pages without images or with videos
  useEffect(() => {
    // If no background image, or if there's a video (video takes over), mark as ready
    if (!activePage?.background_image_url || activePage?.background_video_url) {
      setIsBackgroundReady(true);
    }
  }, [activePage?.background_image_url, activePage?.background_video_url]);

  // Reset pending state when starting a new transition
  useEffect(() => {
    if (transitionPreview) {
      setPendingTransitionComplete(false);
    }
  }, [transitionPreview]);

  // Helper to switch pages without flash
  // Uses usePageSwitch hook to resolve blob URLs from preload cache
  // Also updates storage path state for editing/saving purposes
  // isBack parameter indicates this is a back navigation (pops history instead of pushing)
  const switchToPage = useCallback(
    async (page: TourPage | null, isBack = false) => {
      // Mark this page as initialized to prevent redundant effect calls
      if (page) {
        lastInitializedPageIdRef.current = page.id;
      }

      // Update consolidated background state (replaces 8 separate setters)
      updateBackgroundFromPage(page);

      // Use hook to resolve and set blob URLs for display
      // Fade starts automatically when new background is ready (crossfade effect)
      await pageSwitchToPage(
        page
          ? {
              id: page.id,
              background_image_url: page.background_image_url,
              background_video_url: page.background_video_url,
              background_audio_url: page.background_audio_url,
            }
          : null,
        () => {
          if (page) {
            // Use applyPageSelection for proper history management (pops on back)
            applyPageSelection(page.id, isBack);
          }
        },
      );
    },
    [pageSwitchToPage, updateBackgroundFromPage, applyPageSelection],
  );

  const { isBuffering: isReverseBuffering } = useTransitionPlayback({
    videoRef: transitionVideoRef,
    transition: transitionPreview
      ? {
          videoUrl: resolveAssetPlaybackUrl(transitionPreview.videoUrl),
          storageKey: transitionPreview.storageKey,
          reverseMode: transitionPreview.reverseMode,
          reverseVideoUrl: transitionPreview.reverseVideoUrl
            ? resolveAssetPlaybackUrl(transitionPreview.reverseVideoUrl)
            : undefined,
          durationSec: transitionPreview.durationSec,
          targetPageId: pendingNavigationPageId || undefined,
          displayName: transitionPreview.title,
          isBack: transitionPreview.isBack, // Pass through for history management
        }
      : null,
    onComplete: async (targetPageId, isBack) => {
      const video = transitionVideoRef.current;
      if (targetPageId) {
        const targetPage = pages.find((p) => p.id === targetPageId) || null;
        // Use switchToPage which resolves blob URLs via usePageSwitch
        // Pass isBack flag for proper history management (pops on back)
        await switchToPage(targetPage, isBack ?? false);
        clearSelection();
        setSelectedMenuItem('none');
        setErrorMessage('');
        setIsBackgroundReady(false);
        // Signal that transition video completed - wait for background to load
        // Overlay will be removed instantly when isBackgroundReady becomes true
        setPendingTransitionComplete(true);
      } else {
        video?.removeAttribute('src');
        video?.load();
        closeTransitionPreview();
        setPendingTransitionComplete(false);
      }
    },
    timeouts: {
      playbackStartMs: 3000,
      hardTimeoutMs: 45000,
    },
    features: {
      useBlobUrl: true,
      preDecodeImages: false, // We handle image loading via usePageSwitch
    },
    preload: {
      preloadedUrls: preloadOrchestrator.preloadedUrls,
      getCachedBlobUrl: preloadOrchestrator.getCachedBlobUrl,
      getReadyBlobUrl: preloadOrchestrator.getReadyBlobUrl,
    },
  });

  const iconPreloadTargets = useMemo(() => {
    const preloadableTypes: CanvasElementType[] = [
      'navigation_next',
      'navigation_prev',
      'tooltip',
      'description',
    ];
    const urls = elements
      .filter(
        (element) =>
          preloadableTypes.includes(element.type) && Boolean(element.iconUrl),
      )
      .map((element) => resolveAssetPlaybackUrl(element.iconUrl))
      .filter(Boolean);

    return Array.from(new Set(urls));
  }, [elements]);

  // Icon preloading for smooth rendering
  const { preloadedUrlMap: preloadedIconUrlMap } = useIconPreload({
    iconUrls: iconPreloadTargets,
    enabled: !isLoading,
  });

  // Use the useAssetOptions hook to derive memoized asset options
  const assetOptions = useAssetOptions({ assets });
  // Media duration probing with caching
  const durationProbeTargets = useMemo(
    () =>
      buildDurationProbeTargets({
        backgroundVideoUrl,
        backgroundAudioUrl,
        selectedElement,
        newTransitionVideoUrl,
        elements,
        isMediaElementType,
        isVideoPlayerElementType,
        isNavigationElementType,
      }),
    [
      backgroundAudioUrl,
      backgroundVideoUrl,
      elements,
      newTransitionVideoUrl,
      selectedElement,
    ],
  );

  const { getDuration, getDurationNote, durationBySource } =
    useMediaDurationProbe({
      targets: durationProbeTargets,
    });

  const backgroundVideoDurationNote = getDurationNote(backgroundVideoUrl);
  const backgroundAudioDurationNote = getDurationNote(backgroundAudioUrl);
  const selectedMediaDurationNote = useMemo(() => {
    if (!selectedElement || !isMediaElementType(selectedElement.type)) {
      return 'Duration: unknown';
    }
    return getDurationNote(selectedElement.mediaUrl || '');
  }, [getDurationNote, selectedElement]);

  const newTransitionDurationNote = getDurationNote(newTransitionVideoUrl);

  const selectedTransitionDurationNote = useMemo(() => {
    if (!selectedElement || !isNavigationElementType(selectedElement.type)) {
      return 'Duration: unknown';
    }
    return getDurationNote(selectedElement.transitionVideoUrl || '');
  }, [getDurationNote, selectedElement]);

  useEffect(() => {
    if (newTransitionVideoUrl) return;
    if (!assetOptions.transitionVideo.length) return;
    setNewTransitionVideoUrl(assetOptions.transitionVideo[0].value);
  }, [newTransitionVideoUrl, assetOptions.transitionVideo]);

  useEffect(() => {
    setElements((prev) => {
      let hasChanges = false;
      const next = prev.map((element) => {
        if (!isNavigationElementType(element.type)) return element;

        const resolvedDuration = getDuration(element.transitionVideoUrl || '');
        const nextDuration =
          Number.isFinite(resolvedDuration) && Number(resolvedDuration) > 0
            ? Number(resolvedDuration)
            : undefined;
        if (element.transitionDurationSec === nextDuration) return element;

        hasChanges = true;
        return {
          ...element,
          transitionDurationSec: nextDuration,
        };
      });

      return hasChanges ? next : prev;
    });
  }, [getDuration]);

  // Handle initial page selection when pages are loaded
  const prevPagesLengthRef = useRef(0);
  useEffect(() => {
    // Only set initial page when pages first load (not on every change)
    if (pages.length > 0 && prevPagesLengthRef.current === 0) {
      const defaultPageId = pageIdFromRoute || pages[0]?.id || '';
      // Use applyPageSelection to set initial page and history
      applyPageSelection(defaultPageId, false);
      setIsMenuOpen(false);
      setIsInitializing(false);
    }
    prevPagesLengthRef.current = pages.length;
  }, [pages, pageIdFromRoute, applyPageSelection]);

  // Handle query errors
  useEffect(() => {
    if (isDataError && dataError) {
      const message = dataError.message || 'Failed to load constructor data.';
      logger.error('Failed to load constructor data:', dataError);
      setErrorMessage(message);
    }
  }, [isDataError, dataError]);

  // Refetch wrapper that preserves current page
  const handleReload = useCallback(async () => {
    const currentPageId = activePageId;
    await refetchData();
    // After refetch, restore the active page if it still exists
    // This is handled by the pages effect above
    if (currentPageId && pages.some((p) => p.id === currentPageId)) {
      setActivePageId(currentPageId);
    }
  }, [activePageId, pages, refetchData]);

  // Page actions (save, create page, save to stage)
  const {
    isSaving,
    isSavingToStage,
    isCreatingPage,
    isCreatingTransition,
    saveConstructor,
    saveToStage,
    createPage,
    createTransition,
  } = useConstructorPageActions({
    projectId,
    project,
    pages,
    activePage,
    activePageId,
    elements,
    pageBackground,
    onReload: handleReload,
    onSetActivePageId: setActivePageId,
    onSetMenuOpen: setIsMenuOpen,
    onError: setErrorMessage,
    onSuccess: setSuccessMessage,
  });

  useEffect(() => {
    if (!router.isReady || typeof window === 'undefined') return;

    const token =
      sessionStorage.getItem('token') || localStorage.getItem('token');
    if (!token) {
      setIsAuthReady(false);
      setErrorMessage('Please sign in to continue.');
      router.replace('/login');
      return;
    }

    setIsAuthReady(true);
  }, [router]);

  useEffect(() => {
    if (!router.isReady) return;
    if (projectId) return;
    router.replace(
      isElementEditMode
        ? '/project-element-defaults/project-element-defaults-list'
        : '/projects/projects-list',
    );
  }, [isElementEditMode, projectId, router]);

  // React Query handles data fetching automatically based on projectId and isAuthReady

  // Panel initial positions are handled by useDraggable hooks

  useEffect(() => {
    if (!router.isReady || !isAuthReady || isLoading) return;
    if (didSetInitialCanvasFocus.current) return;
    if (!canvasRef.current) return;

    didSetInitialCanvasFocus.current = true;
    requestAnimationFrame(() => {
      canvasRef.current?.focus({ preventScroll: true });
    });
  }, [isAuthReady, isLoading, router.isReady]);

  useEffect(() => {
    if (!activePage) {
      setElements([]);
      clearSelection();
      updateBackgroundFromPage(null);
      return;
    }

    const schema = parseJsonObject<ConstructorSchema>(
      activePage.ui_schema_json,
      {},
    );
    const normalizedElements = Array.isArray(schema.elements)
      ? schema.elements
          .filter(
            (item) =>
              item && item.type && labelByType[item.type as CanvasElementType],
          )
          .map((item) => {
            const elementType = item.type as CanvasElementType;
            const normalizedElement: CanvasElement = {
              ...item,
              id: String(item.id || createLocalId()),
              label:
                typeof item.label === 'string' && item.label.trim()
                  ? item.label
                  : labelByType[elementType],
              xPercent: clamp(Number(item.xPercent || 0), 0, 100),
              yPercent: clamp(Number(item.yPercent || 0), 0, 100),
              appearDelaySec: normalizeAppearDelaySec(item.appearDelaySec),
              appearDurationSec: normalizeAppearDurationSec(
                item.appearDurationSec,
              ),
              galleryCards: Array.isArray(item.galleryCards)
                ? item.galleryCards.map((card: Partial<GalleryCard>) => ({
                    id: String(card?.id || createLocalId()),
                    imageUrl: String(card?.imageUrl ?? ''),
                    title: String(card?.title ?? ''),
                    description: String(card?.description ?? ''),
                  }))
                : undefined,
              galleryHeaderImageUrl:
                typeof item.galleryHeaderImageUrl === 'string'
                  ? item.galleryHeaderImageUrl
                  : undefined,
              galleryTitle:
                typeof item.galleryTitle === 'string'
                  ? item.galleryTitle
                  : undefined,
              galleryInfoSpans: Array.isArray(item.galleryInfoSpans)
                ? item.galleryInfoSpans.map(
                    (span: Partial<GalleryInfoSpan>) => ({
                      id: String(span?.id || createLocalId()),
                      text: String(span?.text ?? ''),
                      iconUrl: span?.iconUrl ? String(span.iconUrl) : undefined,
                    }),
                  )
                : undefined,
              galleryColumns:
                typeof item.galleryColumns === 'number'
                  ? item.galleryColumns
                  : undefined,
              carouselSlides: Array.isArray(item.carouselSlides)
                ? item.carouselSlides.map((slide: Partial<CarouselSlide>) => ({
                    id: String(slide?.id || createLocalId()),
                    imageUrl: String(slide?.imageUrl ?? ''),
                    caption: String(slide?.caption ?? ''),
                  }))
                : undefined,
              iconUrl: typeof item.iconUrl === 'string' ? item.iconUrl : '',
              carouselPrevIconUrl:
                typeof item.carouselPrevIconUrl === 'string'
                  ? item.carouselPrevIconUrl
                  : '',
              carouselNextIconUrl:
                typeof item.carouselNextIconUrl === 'string'
                  ? item.carouselNextIconUrl
                  : '',
              // Carousel button positions
              carouselPrevX:
                typeof item.carouselPrevX === 'number'
                  ? item.carouselPrevX
                  : undefined,
              carouselPrevY:
                typeof item.carouselPrevY === 'number'
                  ? item.carouselPrevY
                  : undefined,
              carouselNextX:
                typeof item.carouselNextX === 'number'
                  ? item.carouselNextX
                  : undefined,
              carouselNextY:
                typeof item.carouselNextY === 'number'
                  ? item.carouselNextY
                  : undefined,
              // Carousel button dimensions
              carouselPrevWidth:
                typeof item.carouselPrevWidth === 'string'
                  ? item.carouselPrevWidth
                  : undefined,
              carouselPrevHeight:
                typeof item.carouselPrevHeight === 'string'
                  ? item.carouselPrevHeight
                  : undefined,
              carouselNextWidth:
                typeof item.carouselNextWidth === 'string'
                  ? item.carouselNextWidth
                  : undefined,
              carouselNextHeight:
                typeof item.carouselNextHeight === 'string'
                  ? item.carouselNextHeight
                  : undefined,
              // Gallery Carousel Settings
              galleryCarouselPrevIconUrl:
                typeof item.galleryCarouselPrevIconUrl === 'string'
                  ? item.galleryCarouselPrevIconUrl
                  : '',
              galleryCarouselNextIconUrl:
                typeof item.galleryCarouselNextIconUrl === 'string'
                  ? item.galleryCarouselNextIconUrl
                  : '',
              galleryCarouselBackIconUrl:
                typeof item.galleryCarouselBackIconUrl === 'string'
                  ? item.galleryCarouselBackIconUrl
                  : '',
              galleryCarouselBackLabel:
                typeof item.galleryCarouselBackLabel === 'string'
                  ? item.galleryCarouselBackLabel
                  : '',
              galleryCarouselPrevX:
                typeof item.galleryCarouselPrevX === 'number'
                  ? item.galleryCarouselPrevX
                  : undefined,
              galleryCarouselPrevY:
                typeof item.galleryCarouselPrevY === 'number'
                  ? item.galleryCarouselPrevY
                  : undefined,
              galleryCarouselNextX:
                typeof item.galleryCarouselNextX === 'number'
                  ? item.galleryCarouselNextX
                  : undefined,
              galleryCarouselNextY:
                typeof item.galleryCarouselNextY === 'number'
                  ? item.galleryCarouselNextY
                  : undefined,
              galleryCarouselBackX:
                typeof item.galleryCarouselBackX === 'number'
                  ? item.galleryCarouselBackX
                  : undefined,
              galleryCarouselBackY:
                typeof item.galleryCarouselBackY === 'number'
                  ? item.galleryCarouselBackY
                  : undefined,
              tooltipTitle:
                typeof item.tooltipTitle === 'string' ? item.tooltipTitle : '',
              tooltipText:
                typeof item.tooltipText === 'string' ? item.tooltipText : '',
              descriptionTitle:
                typeof item.descriptionTitle === 'string'
                  ? item.descriptionTitle
                  : '',
              descriptionText:
                typeof item.descriptionText === 'string'
                  ? item.descriptionText
                  : '',
              navLabel: typeof item.navLabel === 'string' ? item.navLabel : '',
              navType:
                item.navType === 'back' || item.navType === 'forward'
                  ? item.navType
                  : isNavigationElementType(elementType)
                    ? getNavigationButtonKind(
                        elementType as NavigationElementType,
                      )
                    : undefined,
              // Support both targetPageSlug (new) and targetPageId (legacy)
              targetPageSlug:
                typeof item.targetPageSlug === 'string'
                  ? item.targetPageSlug
                  : '',
              targetPageId:
                typeof item.targetPageId === 'string' ? item.targetPageId : '',
              transitionVideoUrl:
                typeof item.transitionVideoUrl === 'string'
                  ? item.transitionVideoUrl
                  : '',
              transitionReverseMode:
                item.transitionReverseMode === 'separate_video'
                  ? 'separate_video'
                  : ('auto_reverse' as const),
              reverseVideoUrl:
                typeof item.reverseVideoUrl === 'string'
                  ? item.reverseVideoUrl
                  : '',
              transitionDurationSec: item.transitionDurationSec
                ? Number(item.transitionDurationSec)
                : undefined,
              mediaUrl: typeof item.mediaUrl === 'string' ? item.mediaUrl : '',
              mediaAutoplay:
                typeof item.mediaAutoplay === 'boolean'
                  ? item.mediaAutoplay
                  : true,
              mediaLoop:
                typeof item.mediaLoop === 'boolean' ? item.mediaLoop : true,
              mediaMuted:
                typeof item.mediaMuted === 'boolean'
                  ? item.mediaMuted
                  : isVideoPlayerElementType(item.type),
            };

            return mergeElementWithDefaults(
              normalizedElement,
              uiElementDefaultsByType[elementType],
              { preferElementValues: true },
            );
          })
      : [];

    setElements(normalizedElements);
    setSelectedMenuItem('none');

    const currentSelectedId = selectedElementIdRef.current;
    if (!normalizedElements.length) {
      clearSelection();
    } else if (
      elementIdFromRoute &&
      normalizedElements.some((element) => element.id === elementIdFromRoute)
    ) {
      selectElement(elementIdFromRoute);
    } else if (
      currentSelectedId &&
      !normalizedElements.some((element) => element.id === currentSelectedId)
    ) {
      // Current selection no longer valid
      clearSelection();
    }
    // If current selection is still valid, do nothing (keep current)
    // Update consolidated background state (replaces 8 separate setters)
    updateBackgroundFromPage(activePage);

    // Resolve blob URLs via hook for display (handles initial load and route changes)
    // Only call if this page wasn't already initialized via switchToPage function
    if (lastInitializedPageIdRef.current !== activePage.id) {
      lastInitializedPageIdRef.current = activePage.id;
      pageSwitchToPage({
        id: activePage.id,
        background_image_url: activePage.background_image_url,
        background_video_url: activePage.background_video_url,
        background_audio_url: activePage.background_audio_url,
      });
    }
  }, [
    activePage,
    elementIdFromRoute,
    uiElementDefaultsByType,
    pageSwitchToPage,
    clearSelection,
    selectElement,
    setElements,
    updateBackgroundFromPage,
  ]);

  useEffect(() => {
    if (allowedNavigationTypes.length !== 1) return;
    const forcedType = allowedNavigationTypes[0];

    setElements((prev) => {
      let hasChanges = false;
      const nextElements = prev.map((element) => {
        if (
          !isNavigationElementType(element.type) ||
          element.type === forcedType
        )
          return element;
        hasChanges = true;
        return normalizeNavigationType(element, forcedType);
      });
      return hasChanges ? nextElements : prev;
    });
  }, [allowedNavigationTypes, normalizeNavigationType, setElements]);

  // Element drag is now handled by useCanvasElementDrag hook

  useEffect(() => {
    if (isConstructorEditMode) return;
    cancelElementDrag();
    clearSelection();
    setSelectedMenuItem('none');
  }, [isConstructorEditMode, cancelElementDrag, clearSelection]);

  // Outside click detection to clear element/menu selection
  // Ignore clicks on menu to allow menu item selection
  useOutsideClick({
    containerRef: elementEditorRef,
    ignoreRefs: [menuRef],
    ignoreDataAttribute: 'data-constructor-element-id',
    selectedValue: selectedElementId,
    onOutsideClick: useCallback(() => {
      clearSelection();
      setSelectedMenuItem('none');
    }, [clearSelection]),
    enabled:
      isConstructorEditMode &&
      (!!selectedElementId || selectedMenuItem !== 'none'),
  });

  // Thin wrappers for hook functions (handle additional state like selectedMenuItem)
  const selectElementForEdit = useCallback(
    (elementId: string) => {
      selectElement(elementId);
      // Note: setSelectedMenuItem('none') is handled by onElementSelected callback
    },
    [selectElement],
  );

  const selectMenuItemForEdit = useCallback(
    (item: EditorMenuItem) => {
      clearSelection();
      setSelectedMenuItem(item);
    },
    [clearSelection],
  );

  // createPage, saveConstructor, saveToStage are now provided by useConstructorPageActions hook

  const onElementMouseDown = (event: React.MouseEvent, elementId: string) => {
    if (!isConstructorEditMode) return;

    const currentElement = elements.find((item) => item.id === elementId);
    if (!currentElement) return;

    // Select the element for editing
    selectElementForEdit(elementId);

    // Start drag with current position
    onElementDragStart(
      event,
      elementId,
      currentElement.xPercent,
      currentElement.yPercent,
    );
  };

  // openTransitionPreviewForElement is now provided by useTransitionPreview hook

  const openTransitionPreview = (direction: 'forward' | 'back') => {
    if (
      !selectedElement ||
      (selectedElement.type !== 'navigation_next' &&
        selectedElement.type !== 'navigation_prev')
    ) {
      return;
    }

    openTransitionPreviewForElement(selectedElement, direction);
  };

  const onCanvasElementClick = (element: CanvasElement) => {
    if (!isConstructorEditMode) {
      if (isNavigationElementType(element.type)) {
        // Disable navigation while transition is playing or buffering
        if (transitionPreview || isReverseBuffering) {
          return;
        }
        if (element.navDisabled) {
          return;
        }

        // Block forward navigation if neighbor backgrounds not yet preloaded
        // Back navigation is always allowed (previous pages are already visited)
        if (
          !isBackNavigation(element) &&
          !preloadOrchestrator.areNeighborBackgroundsReady
        ) {
          logger.info('Navigation blocked - neighbors not preloaded');
          return;
        }

        // Use shared navigation helpers
        const direction = getNavigationDirection(element);

        // Get navigation context from hook for history-based back navigation
        const navContext = getNavigationContext();

        // Pass history context for history-based back navigation
        const navTarget = resolveNavigationTarget(element, pages, navContext);

        if (!navTarget) {
          // Back buttons always use history, forward buttons use target page
          if (isBackNavigation(element)) {
            if (!navContext.previousPageId) {
              setErrorMessage(
                'No previous page in history. Navigate to another page first.',
              );
            } else {
              setErrorMessage(
                'Previous page not found. It may have been deleted.',
              );
            }
          } else {
            setErrorMessage(
              'No target page configured for this navigation button.',
            );
          }
          return;
        }

        // For back navigation, use transition from navTarget (the forward element that brought us here)
        // For forward navigation, use the element's own transition settings
        const transitionSource = isBackNavigation(element)
          ? {
              type: element.type,
              transitionVideoUrl: navTarget.transitionVideoUrl,
              transitionReverseMode: navTarget.transitionReverseMode,
              reverseVideoUrl: navTarget.reverseVideoUrl,
            }
          : element;

        // Check if transition can be played using shared helper
        if (!hasPlayableTransition(transitionSource, direction)) {
          closeTransitionPreview();
          // Use switchToPage which resolves blob URLs via usePageSwitch (reduces flash)
          // Pass isBack flag for proper history management
          switchToPage(navTarget.page, navTarget.isBack).then(() => {
            clearSelection();
            setSelectedMenuItem('none');
            setErrorMessage('');
          });
          return;
        }

        openPreviewWithTarget(transitionSource, direction, navTarget.pageId);
      }
      return;
    }

    selectElementForEdit(element.id);
  };

  // Handler for gallery card clicks
  const handleGalleryCardClick = useCallback(
    (element: CanvasElement, cardIndex: number) => {
      if (element.galleryCards && element.galleryCards.length > 0) {
        setActiveGalleryCarousel({ element, initialIndex: cardIndex });
      }
    },
    [],
  );

  // Handler for gallery carousel button position changes (constructor only)
  const handleGalleryCarouselButtonPositionChange = useCallback(
    (button: 'prev' | 'next' | 'back', x: number, y: number) => {
      if (!activeGalleryCarousel) return;

      const positionPatch =
        button === 'prev'
          ? { galleryCarouselPrevX: x, galleryCarouselPrevY: y }
          : button === 'next'
            ? { galleryCarouselNextX: x, galleryCarouselNextY: y }
            : { galleryCarouselBackX: x, galleryCarouselBackY: y };

      updateSelectedElement(positionPatch);

      // Update the active carousel element to reflect the new positions
      setActiveGalleryCarousel((prev) =>
        prev
          ? { ...prev, element: { ...prev.element, ...positionPatch } }
          : null,
      );
    },
    [activeGalleryCarousel, updateSelectedElement],
  );

  // Handler for carousel element button position changes (constructor only)
  const handleCarouselButtonPositionChange = useCallback(
    (elementId: string, button: 'prev' | 'next', x: number, y: number) => {
      const positionPatch =
        button === 'prev'
          ? { carouselPrevX: x, carouselPrevY: y }
          : { carouselNextX: x, carouselNextY: y };

      setElements((prev) =>
        prev.map((el) =>
          el.id === elementId ? { ...el, ...positionPatch } : el,
        ),
      );
    },
    [setElements],
  );

  const isElementVisibleOnCanvas = (element: CanvasElement) =>
    isElementVisibleAtTime(
      canvasElapsedSec,
      element.appearDelaySec,
      element.appearDurationSec,
    );

  const isElementReadyForCanvasRender = (element: CanvasElement) => {
    const isPreloadableIconElement =
      (isNavigationElementType(element.type) ||
        isTooltipElementType(element.type) ||
        isDescriptionElementType(element.type)) &&
      Boolean(element.iconUrl);

    if (!isPreloadableIconElement) return true;

    const playbackUrl = resolveAssetPlaybackUrl(element.iconUrl);
    if (!playbackUrl) return true;

    return Boolean(preloadedIconUrlMap[playbackUrl]);
  };

  // URL resolver that uses preloaded blob URLs when available
  // Depends on readyUrlsVersion to re-render when blob URLs become ready after preload
  const resolveUrlWithBlob = useCallback(
    (url: string | undefined): string => {
      if (!url) return '';

      // Try to get blob URL from preload orchestrator (instant display)
      // Check storage key first (most reliable), then resolved URL
      const blobUrl =
        preloadOrchestrator.getReadyBlobUrl(url) ||
        preloadOrchestrator.getReadyBlobUrl(resolveAssetPlaybackUrl(url));
      if (blobUrl) return blobUrl;

      // Fall back to standard resolution
      return resolveAssetPlaybackUrl(url);
    },
    [preloadOrchestrator, preloadOrchestrator.readyUrlsVersion],
  );

  const canvasBackgroundStyle: React.CSSProperties = {};

  // Unified background URL resolution via shared hook
  // Priority: local paths (editing) > pageSwitch (navigation)
  const { backgroundImageSrc, backgroundVideoSrc, backgroundAudioSrc } =
    useBackgroundUrls({
      pageSwitch,
      resolveUrl: resolveUrlWithBlob,
      localPaths: {
        imageUrl: backgroundImageUrl,
        videoUrl: backgroundVideoUrl,
        audioUrl: backgroundAudioUrl,
      },
    });

  const hasEditorSelection =
    isConstructorEditMode &&
    (Boolean(selectedElement) || selectedMenuItem !== 'none');
  const editorTitle =
    selectedMenuItem === 'background_image'
      ? 'Background image'
      : selectedMenuItem === 'background_video'
        ? 'Background video'
        : selectedMenuItem === 'background_audio'
          ? 'Background audio'
          : selectedMenuItem === 'create_transition'
            ? 'Create transition'
            : selectedElement?.label || 'Element editor';

  // Background image is rendered by CanvasBackground component (same as runtime)
  // No CSS background-image needed on canvas div

  // Duration notes for UI display
  const durationNotes = useMemo(
    () => ({
      backgroundVideo: backgroundVideoDurationNote,
      backgroundAudio: backgroundAudioDurationNote,
      selectedMedia: selectedMediaDurationNote,
      selectedTransition: selectedTransitionDurationNote,
      newTransition: newTransitionDurationNote,
    }),
    [
      backgroundVideoDurationNote,
      backgroundAudioDurationNote,
      selectedMediaDurationNote,
      selectedTransitionDurationNote,
      newTransitionDurationNote,
    ],
  );

  // Transition creation state for context
  const transitionCreationState = useMemo(
    () => ({
      name: newTransitionName,
      videoUrl: newTransitionVideoUrl,
      supportsReverse: newTransitionSupportsReverse,
      isCreating: isCreatingTransition,
      setName: setNewTransitionName,
      setVideoUrl: setNewTransitionVideoUrl,
      setSupportsReverse: setNewTransitionSupportsReverse,
      create: () =>
        createTransition({
          name: newTransitionName,
          videoUrl: newTransitionVideoUrl,
          supportsReverse: newTransitionSupportsReverse,
          durationSec: getDuration(newTransitionVideoUrl),
        }),
    }),
    [
      newTransitionName,
      newTransitionVideoUrl,
      newTransitionSupportsReverse,
      isCreatingTransition,
      createTransition,
      getDuration,
    ],
  );

  // Build context value for ConstructorProvider
  // This allows child components to access constructor state without prop drilling
  const constructorContextValue: ConstructorContextValue = useMemo(
    () => ({
      // Project state
      projectId,

      // Page state
      pages,
      activePageId,
      activePage,
      setActivePageId,

      // Background state
      pageBackground,
      setPageBackground,
      updateBackgroundFromPage,

      // Background convenience setters
      setBackgroundImageUrl,
      setBackgroundVideoUrl,
      setBackgroundAudioUrl,
      setBackgroundVideoSettings,

      // Element state
      elements,
      setElements,
      selectedElementId,
      selectedElement,
      selectElement,
      clearSelection,
      updateElement: (id: string, patch: Partial<CanvasElement>) => {
        setElements((prev) =>
          prev.map((el) => (el.id === id ? { ...el, ...patch } : el)),
        );
      },
      removeElement: (id: string) => {
        setElements((prev) => prev.filter((el) => el.id !== id));
        if (selectedElementId === id) {
          clearSelection();
        }
      },
      updateSelectedElement,
      removeSelectedElement,

      // Menu state
      selectedMenuItem,
      setSelectedMenuItem,
      isMenuOpen,
      setIsMenuOpen,

      // Editor state
      elementEditorTab,
      setElementEditorTab,

      // Assets
      assets,
      isLoadingAssets: isDataLoading,

      // Asset options (derived from assets)
      assetOptions,

      // Gallery/Carousel operations
      galleryCards,
      galleryInfoSpans,
      carouselSlides,

      // Duration resolver
      getDuration,

      // Duration notes
      durationNotes,

      // Transition preview
      onPreviewTransition: openTransitionPreview,

      // Transition creation
      transitionCreation: transitionCreationState,

      // Navigation settings
      allowedNavigationTypes,
      normalizeNavigationType,

      // Actions
      save: saveConstructor,
      isSaving,
    }),
    [
      projectId,
      pages,
      activePageId,
      activePage,
      pageBackground,
      setPageBackground,
      updateBackgroundFromPage,
      setBackgroundImageUrl,
      setBackgroundVideoUrl,
      setBackgroundAudioUrl,
      setBackgroundVideoSettings,
      elements,
      setElements,
      selectedElementId,
      selectedElement,
      selectElement,
      clearSelection,
      updateSelectedElement,
      removeSelectedElement,
      selectedMenuItem,
      isMenuOpen,
      elementEditorTab,
      assets,
      isDataLoading,
      assetOptions,
      galleryCards,
      galleryInfoSpans,
      carouselSlides,
      getDuration,
      durationNotes,
      openTransitionPreview,
      transitionCreationState,
      allowedNavigationTypes,
      normalizeNavigationType,
      saveConstructor,
      isSaving,
    ],
  );

  return (
    <ConstructorProvider value={constructorContextValue}>
      <Head>
        <title>
          {getPageTitle(isElementEditMode ? 'Edit Element' : 'Constructor')}
        </title>
      </Head>
      <div className='relative w-screen h-screen bg-black overflow-hidden'>
        <div className='absolute top-4 left-4 z-[1000] flex max-w-[80vw] flex-col gap-2'>
          <p className='text-xs font-semibold text-gray-700'>
            {projectName || 'Loading project...'}
          </p>
          {errorMessage ? (
            <p className='rounded bg-red-50 px-2 py-1 text-xs text-red-600'>
              {errorMessage}
            </p>
          ) : null}
          {successMessage ? (
            <p className='rounded bg-green-50 px-2 py-1 text-xs text-green-700'>
              {successMessage}
            </p>
          ) : null}

          {pages.length > 0 && isElementEditMode && (
            <div className='flex items-center gap-2'>
              <BaseButton
                color='lightDark'
                label='Back to Elements'
                icon={mdiExitToApp}
                href={pageElementsListHref}
              />
              <BaseButton
                color='info'
                label={isSaving ? 'Saving...' : 'Save'}
                icon={mdiContentSave}
                onClick={saveConstructor}
                disabled={isSaving}
              />
            </div>
          )}
        </div>

        {pages.length > 0 && !isElementEditMode && (
          <ConstructorControlsPanel
            projectId={projectId}
            pages={pages}
            activePageId={activePageId}
            interactionMode={constructorInteractionMode}
            position={constructorControlsPosition}
            onPageChange={(pageId) => {
              const page = pages.find((p) => p.id === pageId);
              if (page) switchToPage(page);
            }}
            onModeChange={setConstructorInteractionMode}
            onDragStart={onConstructorControlsDragStart}
          />
        )}

        {/* Canvas container: z-[46] creates stacking context above carousel (z-10 bg, z-45 controls) portaled to body */}
        <div
          ref={canvasRef}
          tabIndex={-1}
          className={`relative z-[46] overflow-clip ${hasFullWidthCarousel ? 'bg-transparent' : 'bg-black'}`}
          style={{
            ...canvasCssVars,
            ...letterboxStyles,
            ...canvasBackgroundStyle,
          }}
        >
          <BackdropPortalProvider>
            {/* Background wrapper - z-5 keeps it BELOW carousel slide (z-10) */}
            <div
              className={`absolute inset-0 z-5 ${isFadingIn ? 'animate-crossfade-in' : ''}`}
            >
              <CanvasBackground
                backgroundImageUrl={backgroundImageSrc}
                backgroundVideoUrl={backgroundVideoSrc}
                backgroundAudioUrl={backgroundAudioSrc}
                previousBgImageUrl={pageSwitch.previousBgImageUrl}
                previousBgVideoUrl={pageSwitch.previousBgVideoUrl}
                isSwitching={pageSwitch.isSwitching}
                isNewBgReady={pageSwitch.isNewBgReady}
                isFadingIn={isFadingIn}
                onBackgroundReady={() => {
                  pageSwitch.markBackgroundReady();
                  setIsBackgroundReady(true);
                }}
                videoAutoplay={backgroundVideoAutoplay}
                videoLoop={backgroundVideoLoop}
                videoMuted={backgroundVideoMuted}
                videoStartTime={backgroundVideoStartTime}
                videoEndTime={backgroundVideoEndTime}
              />
            </div>

            {/* Elements container - z-[46] keeps it ABOVE carousel slide (z-10) AND carousel controls (z-45).
                UI controls (z-50) remain on top. */}
            <div
              className={`absolute inset-0 z-[46] ${isFadingIn ? 'animate-crossfade-in' : ''}`}
            >
              {isLoading ? (
                <div className='absolute inset-0 flex items-center justify-center'>
                  <p className='text-sm text-gray-500'>
                    Loading constructor...
                  </p>
                </div>
              ) : pages.length === 0 ? (
                <div className='absolute inset-0 flex items-center justify-center'>
                  <BaseButton
                    color='info'
                    label={isCreatingPage ? 'Creating...' : 'Create First Page'}
                    icon={mdiPlus}
                    onClick={createPage}
                    disabled={isCreatingPage}
                  />
                </div>
              ) : (
                elements.map((element) => {
                  const shouldRender =
                    selectedElementId === element.id ||
                    (isElementVisibleOnCanvas(element) &&
                      isElementReadyForCanvasRender(element));
                  if (!shouldRender) return null;

                  // Compute disabled state for navigation elements
                  // Forward navigation disabled when:
                  // - Element explicitly disabled (navDisabled)
                  // - Transition is playing or buffering
                  // - Neighbor backgrounds not yet preloaded (forward only)
                  const isForwardNav =
                    isNavigationElementType(element.type) &&
                    !isBackNavigation(element);
                  const isNavDisabled =
                    isNavigationElementType(element.type) &&
                    (element.navDisabled ||
                      Boolean(transitionPreview) ||
                      isReverseBuffering ||
                      (isForwardNav &&
                        !preloadOrchestrator.areNeighborBackgroundsReady));

                  return (
                    <CanvasElementComponent
                      key={element.id}
                      element={element}
                      isSelected={selectedElementId === element.id}
                      isEditMode={isConstructorEditMode}
                      isDisabled={isNavDisabled}
                      onClick={() => onCanvasElementClick(element)}
                      onMouseDown={(event) =>
                        onElementMouseDown(event, element.id)
                      }
                      resolveUrl={resolveUrlWithBlob}
                      onGalleryCardClick={(cardIndex) =>
                        handleGalleryCardClick(element, cardIndex)
                      }
                      onCarouselButtonPositionChange={(button, x, y) =>
                        handleCarouselButtonPositionChange(
                          element.id,
                          button,
                          x,
                          y,
                        )
                      }
                      letterboxStyles={letterboxStyles}
                    />
                  );
                })
              )}
            </div>
          </BackdropPortalProvider>
        </div>

        {/* ElementEditorPanel now uses ConstructorContext for all state */}
        {pages.length > 0 && hasEditorSelection && (
          <ElementEditorPanel
            elementEditorRef={elementEditorRef}
            position={editorPosition}
            isCollapsed={isEditorCollapsed}
            onToggleCollapse={() => setIsEditorCollapsed((prev) => !prev)}
            onDragStart={onElementEditorDragStart}
            title={editorTitle}
          />
        )}

        {pages.length > 0 && !isElementEditMode && (
          <ConstructorMenu
            ref={menuRef}
            position={menuPosition}
            isOpen={isMenuOpen}
            allowedNavigationTypes={allowedNavigationTypes}
            isCreatingPage={isCreatingPage}
            isSaving={isSaving}
            isSavingToStage={isSavingToStage}
            onDragStart={onMenuDragStart}
            onToggleOpen={() => setIsMenuOpen((prev) => !prev)}
            onSelectMenuItem={selectMenuItemForEdit}
            onAddElement={addElement}
            onCreatePage={createPage}
            onSave={saveConstructor}
            onSaveToStage={saveToStage}
            onExit={() =>
              router.push(
                projectId
                  ? `/projects/${projectId}`
                  : '/projects/projects-list',
              )
            }
          />
        )}
      </div>

      <TransitionPreviewOverlay
        videoRef={transitionVideoRef}
        isActive={Boolean(transitionPreview)}
        isBuffering={isReverseBuffering}
        letterboxStyles={letterboxStyles}
      />

      {/* Gallery Carousel Overlay */}
      {activeGalleryCarousel && (
        <GalleryCarouselOverlay
          cards={activeGalleryCarousel.element.galleryCards || []}
          initialIndex={activeGalleryCarousel.initialIndex}
          onClose={() => setActiveGalleryCarousel(null)}
          resolveUrl={resolveUrlWithBlob}
          prevIconUrl={activeGalleryCarousel.element.galleryCarouselPrevIconUrl}
          nextIconUrl={activeGalleryCarousel.element.galleryCarouselNextIconUrl}
          backIconUrl={activeGalleryCarousel.element.galleryCarouselBackIconUrl}
          backLabel={
            activeGalleryCarousel.element.galleryCarouselBackLabel || 'BACK'
          }
          prevX={activeGalleryCarousel.element.galleryCarouselPrevX}
          prevY={activeGalleryCarousel.element.galleryCarouselPrevY}
          nextX={activeGalleryCarousel.element.galleryCarouselNextX}
          nextY={activeGalleryCarousel.element.galleryCarouselNextY}
          backX={activeGalleryCarousel.element.galleryCarouselBackX}
          backY={activeGalleryCarousel.element.galleryCarouselBackY}
          prevWidth={activeGalleryCarousel.element.galleryCarouselPrevWidth}
          prevHeight={activeGalleryCarousel.element.galleryCarouselPrevHeight}
          nextWidth={activeGalleryCarousel.element.galleryCarouselNextWidth}
          nextHeight={activeGalleryCarousel.element.galleryCarouselNextHeight}
          backWidth={activeGalleryCarousel.element.galleryCarouselBackWidth}
          backHeight={activeGalleryCarousel.element.galleryCarouselBackHeight}
          letterboxStyles={letterboxStyles}
          isEditMode={isConstructorEditMode}
          onButtonPositionChange={handleGalleryCarouselButtonPositionChange}
        />
      )}

      <style jsx>{`
        .menu-action-btn {
          width: 100%;
          display: flex;
          align-items: center;
          gap: 8px;
          font-size: 12px;
          padding: 6px 8px;
          border-radius: 6px;
          color: #1f2937;
          text-align: left;
        }

        .menu-action-btn:hover {
          background: #f3f4f6;
        }
      `}</style>
    </ConstructorProvider>
  );
};

ConstructorPage.getLayout = function getLayout(page: ReactElement) {
  return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};

export default ConstructorPage;
