/**
 * RuntimePresentation Component
 *
 * Renders a presentation for a specific project and environment.
 * Used by /p/[projectSlug] and /p/[projectSlug]/stage routes.
 */

import Head from 'next/head';
import React, {
  ReactElement,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { flushSync } from 'react-dom';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import CardBox from './CardBox';
import RuntimeControls from './Runtime/RuntimeControls';
import RuntimeElement from './RuntimeElement';
import TransitionPreviewOverlay from './Constructor/TransitionPreviewOverlay';
import GalleryCarouselOverlay from './UiElements/GalleryCarouselOverlay';
import { BackdropPortalProvider } from './BackdropPortal';
import { RotatePrompt } from './RotatePrompt';
import CanvasBackground from './Constructor/CanvasBackground';
import TransitionBlackOverlay from './TransitionBlackOverlay';
import { useCanvasScale } from '../hooks/useCanvasScale';
import { CANVAS_CONFIG } from '../config/canvas.config';
import LayoutGuest from '../layouts/Guest';
import { usePreloadOrchestrator } from '../hooks/usePreloadOrchestrator';
import { useVideoSoundControl } from '../hooks/useVideoSoundControl';
import { usePageDataLoader } from '../hooks/usePageDataLoader';
import { useProjectAssets } from '../hooks/useProjectAssets';
import { usePageNavigation } from '../hooks/usePageNavigation';
import {
  extractPageLinksOnly,
  extractElementsForPages,
} from '../lib/extractPageLinks';
import { usePageSwitch } from '../hooks/usePageSwitch';
import { useTransitionPlayback } from '../hooks/useTransitionPlayback';
import { useBackgroundTransition } from '../hooks/useBackgroundTransition';
import { useBackgroundUrls } from '../hooks/useBackgroundUrls';
import { resolveAssetPlaybackUrl } from '../lib/assetUrl';
import { isSafari, scheduleAfterPaint } from '../lib/browserUtils';
import { logger } from '../lib/logger';
import {
  resolveNavigationTarget,
  isTransitionBlocking,
  isBackNavigation,
  isNavigationType,
} from '../lib/navigationHelpers';
import { useTransitionSettings } from '../hooks/useTransitionSettings';
import { useAppSelector, useAppDispatch } from '../stores/hooks';
import { fetch as fetchGlobalTransitionDefaults } from '../stores/global_transition_defaults/globalTransitionDefaultsSlice';
import {
  fetchByProjectAndEnv as fetchProjectTransitionSettings,
  selectByProjectAndEnv as selectProjectTransitionSettings,
} from '../stores/project_transition_settings/projectTransitionSettingsSlice';
import type { TransitionPhase } from '../types/presentation';
import type { CanvasElement } from '../types/constructor';
import type { ElementTransitionSettings } from '../types/transition';
import {
  entityToProjectSettings,
  extractElementTransitionSettings,
} from '../types/transition';

interface RuntimePresentationProps {
  projectSlug: string;
  environment: 'stage' | 'production';
}

export default function RuntimePresentation({
  projectSlug,
  environment,
}: RuntimePresentationProps) {
  const dispatch = useAppDispatch();
  const globalTransitionDefaults = useAppSelector(
    (state) => state.global_transition_defaults.data,
  );

  // Use shared hook for loading project and pages data
  // Note: We can't fetch project transition settings until we have the project ID
  const { project, pages, isLoading, error, initialPageId } = usePageDataLoader(
    {
      projectSlug,
      environment,
      apiHeaders: {
        'X-Runtime-Project-Slug': projectSlug,
        'X-Runtime-Environment': environment,
      },
    },
  );

  // Fetch global transition defaults on mount (public endpoint, no auth needed)
  useEffect(() => {
    dispatch(fetchGlobalTransitionDefaults());
  }, [dispatch]);

  // Fetch project transition settings when project is loaded
  useEffect(() => {
    if (project?.id) {
      dispatch(
        fetchProjectTransitionSettings({ projectId: project.id, environment }),
      );
    }
  }, [dispatch, project?.id, environment]);

  // Select project transition settings from store (environment-aware)
  const projectTransitionSettingsEntity = useAppSelector((state) =>
    project?.id
      ? selectProjectTransitionSettings(state, project.id, environment)
      : undefined,
  );
  const projectTransitionSettings = useMemo(
    () => entityToProjectSettings(projectTransitionSettingsEntity),
    [projectTransitionSettingsEntity],
  );

  // Track current element's transition settings (set when navigation is triggered)
  const [
    currentElementTransitionSettings,
    setCurrentElementTransitionSettings,
  ] = useState<ElementTransitionSettings | null>(null);

  // Resolve transition settings using the cascade: Element → Project → Global
  const transitionSettings = useTransitionSettings({
    globalDefaults: globalTransitionDefaults,
    projectSettings: projectTransitionSettings,
    elementSettings: currentElementTransitionSettings,
  });

  // Resolve project assets (favicon, og_image, logo) to presigned URLs
  const { faviconUrl, ogImageUrl } = useProjectAssets(project);

  // Page navigation with history tracking via shared hook
  const {
    currentPageId: selectedPageId,
    pageHistory,
    applyPageSelection,
    getNavigationContext,
  } = usePageNavigation({
    pages,
    defaultPageId: initialPageId || undefined,
    trackHistory: true,
  });

  // Get current page for design dimensions (presentations use page dimensions, not project)
  const currentPage = pages.find((p) => p.id === selectedPageId);

  // Canvas scale for responsive UI elements and letterbox mode
  // Uses page's design dimensions (saved at constructor save time) for presentation isolation
  const {
    cssVars,
    letterboxStyles,
    isPortrait,
    showRotatePrompt,
    canvasWidth,
    canvasHeight,
  } = useCanvasScale({
    designWidth: currentPage?.design_width ?? undefined,
    designHeight: currentPage?.design_height ?? undefined,
  });

  const [transitionPreview, setTransitionPreview] = useState<{
    targetPageId: string;
    videoUrl: string;
    storageKey: string;
    isBack: boolean;
    reverseVideoUrl?: string;
  } | null>(null);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [isBackgroundReady, setIsBackgroundReady] = useState(true);
  // Track when transition video has completed but we're waiting for background to load
  const [pendingTransitionComplete, setPendingTransitionComplete] =
    useState(false);
  const [activeGalleryCarousel, setActiveGalleryCarousel] = useState<{
    element: CanvasElement;
    initialIndex: number;
  } | null>(null);

  // Safari Black Flash Prevention (video transitions only):
  // Track the last successfully displayed background to use as a "snapshot" layer.
  // Only shown during video transitions to prevent black flashes.
  const [lastKnownBgUrl, setLastKnownBgUrl] = useState<string>('');

  const transitionVideoRef = useRef<HTMLVideoElement>(null);
  const lastInitializedPageIdRef = useRef<string | null>(null);

  // Note: Initial page selection is handled by usePageNavigation hook via defaultPageId

  // Phase 1: Extract pageLinks from ALL pages (needed for navigation graph)
  // This is lightweight - only extracts navigation structure, not asset URLs
  const pageLinks = useMemo(() => {
    const links = extractPageLinksOnly(pages);
    if (links.length > 0) {
      logger.info('[PRELOAD] Extracted page links', {
        count: links.length,
        links: links.map((link) => ({
          from: link.from_pageId?.slice(-8),
          to: link.to_pageId?.slice(-8),
          hasTransition: !!link.transition?.video_url,
        })),
      });
    }
    return links;
  }, [pages]);

  // Phase 2: Extract elements only for current + neighbor pages (progressive)
  // This avoids parsing ui_schema_json for all pages upfront
  const preloadElements = useMemo(() => {
    if (!selectedPageId || pages.length === 0) return [];

    // Build simple neighbor set from pageLinks
    const neighborIds = new Set<string>();
    neighborIds.add(selectedPageId); // Current page
    pageLinks.forEach((link) => {
      if (link.from_pageId === selectedPageId && link.to_pageId) {
        neighborIds.add(link.to_pageId); // Direct neighbors
      }
    });

    // Extract elements only for current + neighbors
    const elements = extractElementsForPages(pages, Array.from(neighborIds));
    logger.info('[PRELOAD] Extracted elements for pages', {
      currentPage: selectedPageId.slice(-8),
      pageCount: neighborIds.size,
      elementCount: elements.length,
    });
    return elements;
  }, [pages, pageLinks, selectedPageId]);

  // Initialize preload orchestrator with transformed data
  const preloadOrchestrator = usePreloadOrchestrator({
    pages,
    pageLinks,
    elements: preloadElements,
    currentPageId: selectedPageId,
    pageHistory,
    enabled: !isLoading && !error,
  });

  // Initialize page switch hook for smooth background transitions
  const pageSwitch = usePageSwitch({
    preloadCache: preloadOrchestrator
      ? {
          getReadyBlobUrl: preloadOrchestrator.getReadyBlobUrl,
          getCachedBlobUrl: preloadOrchestrator.getCachedBlobUrl,
          preloadedUrls: preloadOrchestrator.preloadedUrls,
        }
      : undefined,
  });

  // Integrate useTransitionPlayback hook for smooth transitions (matches Constructor pattern)
  const { isBuffering, phase: transitionPhase } = useTransitionPlayback({
    videoRef: transitionVideoRef,
    transition: transitionPreview
      ? {
          videoUrl: transitionPreview.videoUrl,
          storageKey: transitionPreview.storageKey,
          reverseMode: transitionPreview.reverseVideoUrl ? 'separate' : 'none',
          reverseVideoUrl: transitionPreview.reverseVideoUrl,
          targetPageId: transitionPreview.targetPageId,
          displayName: 'Transition',
          isBack: transitionPreview.isBack,
        }
      : null,
    onComplete: async (targetPageId, isBack) => {
      if (targetPageId) {
        const targetPage = pages.find((p) => p.id === targetPageId);
        // Mark this page as initialized to prevent redundant effect calls
        lastInitializedPageIdRef.current = targetPageId;
        // Use shared hook to resolve blob URLs and switch page
        await pageSwitch.switchToPage(targetPage, () => {
          // Use applyPageSelection for proper history management (pops on back)
          applyPageSelection(targetPageId, isBack ?? false);
        });
        setIsBackgroundReady(false);
        // Video transition completed - last frame shows new page background
        // Signal that we're waiting for background to load before removing overlay
        // Overlay will be removed instantly (no fade) when isBackgroundReady becomes true
        setPendingTransitionComplete(true);
      } else {
        // No target page - clean up and remove overlay
        const video = transitionVideoRef.current;
        video?.removeAttribute('src');
        video?.load();
        setTransitionPreview(null);
        setPendingTransitionComplete(false);
      }
    },
    features: {
      useBlobUrl: true,
      // Don't pre-decode images in the hook - we handle it via overlay:
      // Overlay shows last transition frame while new page background loads behind it
      preDecodeImages: false,
    },
    preload: {
      preloadedUrls: preloadOrchestrator?.preloadedUrls || new Set(),
      getCachedBlobUrl: preloadOrchestrator?.getCachedBlobUrl,
      getReadyBlobUrl: preloadOrchestrator?.getReadyBlobUrl,
      getReadyBlob: preloadOrchestrator?.getReadyBlob,
    },
  });

  // Use shared background transition hook for fade-from-black effects
  // Video transitions end instantly (last frame = new page, then overlay removed).
  // fadeIn controls the black overlay for non-video navigation.
  // hasActiveTransition prevents fade during video-to-background handoff.
  const { isFadingIn, resetFadeIn, transitionStyle } = useBackgroundTransition({
    pageSwitch,
    fadeIn: {
      hasActiveTransition:
        Boolean(transitionPreview) || pendingTransitionComplete,
    },
    transitionSettings,
  });

  const toggleFullscreen = useCallback(async () => {
    try {
      if (!document.fullscreenElement) {
        await document.documentElement.requestFullscreen();
        setIsFullscreen(true);
      } else {
        await document.exitFullscreen();
        setIsFullscreen(false);
      }
    } catch (err) {
      logger.error(
        'Fullscreen toggle failed:',
        err instanceof Error ? err : { error: err },
      );
    }
  }, []);

  useEffect(() => {
    const handleFullscreenChange = () => {
      setIsFullscreen(Boolean(document.fullscreenElement));
    };

    document.addEventListener('fullscreenchange', handleFullscreenChange);
    return () =>
      document.removeEventListener('fullscreenchange', handleFullscreenChange);
  }, []);

  const selectedPage = useMemo(
    () => pages.find((p) => p.id === selectedPageId) || null,
    [pages, selectedPageId],
  );

  const pageElements = useMemo(() => {
    if (!selectedPage) return [];

    try {
      const uiSchema =
        typeof selectedPage.ui_schema_json === 'string'
          ? JSON.parse(selectedPage.ui_schema_json)
          : selectedPage.ui_schema_json;

      return Array.isArray(uiSchema?.elements) ? uiSchema.elements : [];
    } catch {
      return [];
    }
  }, [selectedPage]);

  // Set initial backgrounds when page first loads (before preload cache is populated)
  // The condition ensures this only runs once on initial load when backgrounds are empty.
  // After that, navigateToPage handles all subsequent navigation explicitly.
  useEffect(() => {
    if (selectedPage && lastInitializedPageIdRef.current !== selectedPage.id) {
      // Only initialize when backgrounds are empty (initial load)
      // navigateToPage handles subsequent navigation by calling switchToPage directly
      if (!pageSwitch.currentBgImageUrl && !pageSwitch.currentBgVideoUrl) {
        lastInitializedPageIdRef.current = selectedPage.id;
        pageSwitch.switchToPage(selectedPage);
      }
    }
  }, [
    selectedPage,
    pageSwitch.currentBgImageUrl,
    pageSwitch.currentBgVideoUrl,
    pageSwitch.switchToPage,
  ]);

  // Handle background ready state for pages without any background
  useEffect(() => {
    // Only mark ready immediately if there's no background media at all.
    // For pages with image or video, CanvasBackground will call onBackgroundReady
    // after the media's first frame is painted (using scheduleAfterPaint or requestVideoFrameCallback).
    if (
      !selectedPage?.background_image_url &&
      !selectedPage?.background_video_url
    ) {
      setIsBackgroundReady(true);
    }
  }, [selectedPage?.background_image_url, selectedPage?.background_video_url]);

  // Video transition overlay removal - instant (no fade) when background is ready
  // Uses scheduleAfterPaint to ensure browser has painted the new background before removing overlay
  // Note: Double RAF doesn't work in Safari (nested RAFs can execute in same frame)
  // CRITICAL: Must wait for BOTH isBackgroundReady AND pageSwitch.isNewBgReady
  // - isBackgroundReady: RuntimePresentation state (image onLoad or immediate for video pages)
  // - pageSwitch.isNewBgReady: Controls PreviousBackgroundOverlay visibility
  // If we only check isBackgroundReady, PreviousBackgroundOverlay may still be showing
  useEffect(() => {
    if (
      pendingTransitionComplete &&
      isBackgroundReady &&
      pageSwitch.isNewBgReady
    ) {
      // Wait for paint cycle to complete before removing overlay
      // scheduleAfterPaint handles Safari's RAF quirks automatically
      scheduleAfterPaint(() => {
        // CRITICAL: Remove overlay from DOM FIRST, then clear video src
        // If we clear src before removing overlay, Safari shows black frame
        // because video.removeAttribute('src') immediately clears the frame
        setTransitionPreview(null);
        setPendingTransitionComplete(false);

        // Clear previous background now that transition is complete
        // This resets isSwitching state for next navigation
        pageSwitch.clearPreviousBackground();

        // Clear video src AFTER overlay is removed from DOM
        // Use another scheduleAfterPaint to ensure React has unmounted the overlay
        scheduleAfterPaint(() => {
          const video = transitionVideoRef.current;
          if (video) {
            video.removeAttribute('src');
            video.load();
          }
        });
      });
    }
  }, [
    pendingTransitionComplete,
    isBackgroundReady,
    pageSwitch.isNewBgReady,
    pageSwitch.clearPreviousBackground,
  ]);

  // Safari Black Flash Prevention (video transitions only):
  // Update lastKnownBgUrl whenever we have a valid background image.
  // This ensures snapshot is always ready before transitions start.
  useEffect(() => {
    if (pageSwitch.currentBgImageUrl) {
      setLastKnownBgUrl(pageSwitch.currentBgImageUrl);
    }
  }, [pageSwitch.currentBgImageUrl]);

  const navigateToPage = useCallback(
    async (
      targetPageId: string,
      transitionVideoUrl?: string,
      isBack = false,
      reverseVideoUrl?: string,
    ) => {
      const targetPage = pages.find((p) => p.id === targetPageId);
      if (!targetPage) return;

      if (transitionVideoUrl) {
        // Reset states from previous transition/navigation
        resetFadeIn();
        setPendingTransitionComplete(false);
        // Play transition using useTransitionPlayback hook
        setTransitionPreview({
          targetPageId,
          videoUrl: resolveAssetPlaybackUrl(transitionVideoUrl),
          storageKey: transitionVideoUrl, // Raw storage path for cache lookup
          isBack,
          reverseVideoUrl: reverseVideoUrl
            ? resolveAssetPlaybackUrl(reverseVideoUrl)
            : undefined,
        });
      } else {
        // Direct navigation with fade-from-black effect:
        // Page switches instantly, black overlay fades out to reveal new page
        setIsBackgroundReady(false);
        // Mark this page as initialized to prevent redundant effect calls
        lastInitializedPageIdRef.current = targetPageId;

        await pageSwitch.switchToPage(targetPage, () => {
          // Use applyPageSelection for proper history management (pops on back)
          applyPageSelection(targetPageId, isBack);
        });
      }
    },
    [pages, pageSwitch, resetFadeIn, applyPageSelection],
  );

  // Compute whether all neighbor backgrounds are ready for instant navigation
  const areNeighborBackgroundsReady =
    preloadOrchestrator?.areNeighborBackgroundsReady ?? true;

  // Compute disabled state for forward navigation elements
  // DISABLED: Allow navigation even if neighbors not preloaded
  const isForwardNavDisabled = false && !areNeighborBackgroundsReady;

  const handleElementClick = useCallback(
    (element: CanvasElement) => {
      // Block navigation while transition is actively playing or buffering
      if (
        isTransitionBlocking(transitionPhase as TransitionPhase, isBuffering)
      ) {
        return;
      }

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

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

      // Use shared helper to resolve navigation target with history context
      const navTarget = resolveNavigationTarget(element, pages, navContext);

      // Debug: log element navigation data
      logger.info('Element clicked', {
        elementType: element.type,
        targetPageSlug: element.targetPageSlug,
        legacyTargetPageId: element.targetPageId,
        resolvedTargetPageId: navTarget?.pageId,
        transitionVideoUrl: element.transitionVideoUrl,
        hasTransition: Boolean(element.transitionVideoUrl),
        isBack: isBackNavigation(element),
        previousPageId: navContext.previousPageId,
      });

      if (navTarget) {
        // Extract element transition settings for CSS-based transitions
        // For back navigation, use navTarget's settings (the forward element that brought us here)
        // For forward navigation, use the clicked element's settings
        const elementTransitionSource = isBackNavigation(element)
          ? navTarget
          : element;
        const elementSettings = extractElementTransitionSettings(
          elementTransitionSource,
        );
        // Use flushSync to ensure state is updated synchronously before transition starts
        // Without this, React's async state batching causes the transition to use OLD settings
        flushSync(() => {
          setCurrentElementTransitionSettings(elementSettings);
        });

        navigateToPage(
          navTarget.pageId,
          navTarget.transitionVideoUrl,
          navTarget.isBack,
          navTarget.reverseVideoUrl,
        );
      }
    },
    [
      navigateToPage,
      pages,
      transitionPhase,
      isBuffering,
      getNavigationContext,
      areNeighborBackgroundsReady,
      setCurrentElementTransitionSettings,
    ],
  );

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

  // URL resolver that uses preloaded blob URLs when available (instant display)
  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],
  );

  // Unified background URL resolution via shared hook (same as constructor)
  // No localPaths needed since RuntimePresentation has no editing mode
  const {
    backgroundImageSrc: backgroundImageUrl,
    backgroundVideoSrc: backgroundVideoUrl,
  } = useBackgroundUrls({
    pageSwitch,
    resolveUrl: resolveUrlWithBlob,
  });

  // Background video playback settings from selected page
  const videoAutoplay = selectedPage?.background_video_autoplay ?? true;
  const videoLoop = selectedPage?.background_video_loop ?? true;
  // Note: pageVideoMuted is the page setting, but we use soundControl.isMuted for actual muted state
  // This allows iOS to autoplay (starts muted) while giving user control via sound button
  const pageVideoMuted = selectedPage?.background_video_muted ?? true;
  const videoStartTime =
    selectedPage?.background_video_start_time != null
      ? parseFloat(String(selectedPage.background_video_start_time))
      : null;
  const videoEndTime =
    selectedPage?.background_video_end_time != null
      ? parseFloat(String(selectedPage.background_video_end_time))
      : null;

  // Sound control hook for iOS autoplay compatibility
  // Videos start muted (for iOS autoplay), user can unmute via sound button
  const soundControl = useVideoSoundControl({
    pageHasSound: pageVideoMuted === false, // Show button when page allows sound
    hasBackgroundVideo: Boolean(backgroundVideoUrl),
    videoUrl: backgroundVideoUrl, // Track video changes for page navigation reset
  });

  // Note: useBackgroundVideoPlayback is handled internally by CanvasBackground component

  if (isLoading) {
    return (
      <>
        <Head>
          <meta name='theme-color' content='#000000' />
          <meta name='color-scheme' content='dark' />
          <style>{`html, body { background-color: #000000 !important; }`}</style>
        </Head>
        <div className='flex items-center justify-center min-h-screen bg-black'>
          <div className='text-white text-xl'>Loading presentation...</div>
        </div>
      </>
    );
  }

  if (error) {
    return (
      <>
        <Head>
          <meta name='theme-color' content='#000000' />
          <meta name='color-scheme' content='dark' />
          <style>{`html, body { background-color: #000000 !important; }`}</style>
        </Head>
        <div className='flex items-center justify-center min-h-screen bg-black'>
          <CardBox className='max-w-md'>
            <h2 className='text-xl font-bold text-red-500 mb-4'>Error</h2>
            <p className='text-gray-300'>{error}</p>
          </CardBox>
        </div>
      </>
    );
  }

  return (
    <>
      {/* Rotate prompt for portrait orientation */}
      <RotatePrompt show={showRotatePrompt && isPortrait} />

      <Head>
        <title>{project?.name || 'Presentation'}</title>
        {/* Dark theme for browser UI and background */}
        <meta name='theme-color' content='#000000' />
        <meta name='color-scheme' content='dark' />
        <meta
          name='apple-mobile-web-app-status-bar-style'
          content='black-translucent'
        />
        <style>{`
          html, body { background-color: #000000 !important; }
        `}</style>
        {faviconUrl && <link key='favicon' rel='icon' href={faviconUrl} />}
        {ogImageUrl && (
          <>
            <meta key='og:image' property='og:image' content={ogImageUrl} />
            <meta
              key='twitter:image:src'
              property='twitter:image:src'
              content={ogImageUrl}
            />
          </>
        )}
        {project?.name && (
          <>
            <meta key='og:title' property='og:title' content={project.name} />
            <meta
              key='twitter:title'
              property='twitter:title'
              content={project.name}
            />
          </>
        )}
        {project?.description && (
          <>
            <meta
              key='og:description'
              property='og:description'
              content={project.description}
            />
            <meta
              key='twitter:description'
              property='twitter:description'
              content={project.description}
            />
          </>
        )}
      </Head>

      {/* Outer container: full viewport with black background for letterbox bars */}
      <div className='relative w-screen h-screen overflow-hidden bg-black'>
        {/* Inner canvas: maintains aspect ratio centered in viewport.
            z-[46] creates stacking context above carousel (z-10 bg, z-45 controls) portaled to body. */}
        <div
          className='relative z-[46] overflow-hidden'
          style={{
            ...cssVars,
            ...letterboxStyles,
          }}
        >
          <BackdropPortalProvider>
            {/* Safari Black Flash Prevention (video transitions only):
                Persistent snapshot layer shown ONLY during video transitions.
                z-[1] keeps it behind backgrounds (z-5) but above the black container. */}
            {lastKnownBgUrl &&
              isSafari() &&
              (transitionPreview || pendingTransitionComplete) && (
                <div
                  className='absolute inset-0 z-[1] pointer-events-none'
                  style={{
                    backgroundImage: `url("${lastKnownBgUrl}")`,
                    backgroundSize: 'contain',
                    backgroundPosition: 'center',
                    backgroundRepeat: 'no-repeat',
                  }}
                />
              )}

            {/* Page background wrapper - z-5 keeps it BELOW carousel slide (z-10).
                Uses shared CanvasBackground component for single source of truth with constructor.
                Previous background overlay shows during loading.
                Black overlay for fade effect is rendered separately at z-[100]. */}
            <div
              data-testid='page-background-wrapper'
              className='absolute inset-0 z-5'
            >
              <CanvasBackground
                backgroundImageUrl={backgroundImageUrl}
                backgroundVideoUrl={backgroundVideoUrl}
                previousBgImageUrl={pageSwitch.previousBgImageUrl}
                previousBgVideoUrl={pageSwitch.previousBgVideoUrl}
                isSwitching={pageSwitch.isSwitching}
                isNewBgReady={pageSwitch.isNewBgReady}
                onBackgroundReady={() => {
                  setIsBackgroundReady(true);
                  pageSwitch.markBackgroundReady();
                }}
                videoAutoplay={videoAutoplay}
                videoLoop={videoLoop}
                videoMuted={soundControl.isMuted}
                videoStartTime={videoStartTime}
                videoEndTime={videoEndTime}
                videoStoragePath={selectedPage?.background_video_url}
              />
            </div>
            {/* End page background wrapper */}

            {/* Page elements wrapper - z-[46] keeps it ABOVE carousel slide (z-10) AND carousel controls (z-45).
                UI controls (z-50) remain on top.
                No fade animation - elements switch instantly behind the black overlay. */}
            <div
              data-testid='page-elements-wrapper'
              className='absolute inset-0 z-[46]'
            >
              {pageElements.map((element: CanvasElement) => (
                <RuntimeElement
                  key={element.id}
                  element={element}
                  onClick={() => handleElementClick(element)}
                  resolveUrl={resolveUrlWithBlob}
                  onGalleryCardClick={(cardIndex) =>
                    handleGalleryCardClick(element, cardIndex)
                  }
                  letterboxStyles={letterboxStyles}
                  isForwardNavDisabled={isForwardNavDisabled}
                  pageTransitionSettings={transitionSettings}
                />
              ))}
            </div>
            {/* End page elements wrapper */}

            {/* Overlay for fade-through-color transition - z-[100] is ABOVE elements (z-[46]).
                This covers the elements during page transition to hide the instant switch.
                Only rendered for 'fade' type. */}
            <TransitionBlackOverlay
              isFadingIn={isFadingIn}
              transitionType={transitionSettings.type}
              transitionStyle={transitionStyle}
              overlayColor={transitionSettings.overlayColor}
            />

            {/* Transition overlay - uses useTransitionPlayback hook for smooth transitions */}
            {/* Opacity is 0 during 'preparing' phase to show old page while video loads */}
            {/* NO fade-out: video itself IS the transition (last frame = new page) */}
            {/* Overlay removed instantly via setTransitionPreview(null) in onComplete */}
            {transitionPreview && (
              <TransitionPreviewOverlay
                videoKey={transitionPreview.videoUrl}
                videoRef={transitionVideoRef}
                isActive={true}
                isBuffering={
                  // Hide overlay until video first frame is painted:
                  // - 'idle': React render cycle before hook effect runs
                  // - 'preparing': Video loading/buffering
                  // - isBuffering: Waiting for first frame paint (from hook)
                  transitionPhase === 'idle' ||
                  transitionPhase === 'preparing' ||
                  isBuffering
                }
                letterboxStyles={letterboxStyles}
                opacity={1}
              />
            )}

            {/* 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={false}
                pageTransitionSettings={transitionSettings}
                galleryElement={activeGalleryCarousel.element}
              />
            )}
          </BackdropPortalProvider>
        </div>
        {/* End inner canvas container */}

        {/* Controls: Offline toggle, Fullscreen, and Sound buttons */}
        {/* Positioned outside canvas to avoid scaling with canvas transform */}
        <RuntimeControls
          projectId={project?.id || null}
          projectSlug={projectSlug}
          projectName={project?.name}
          pages={pages}
          isFullscreen={isFullscreen}
          toggleFullscreen={toggleFullscreen}
          canvasWidth={canvasWidth}
          canvasHeight={canvasHeight}
          showSoundButton={soundControl.showSoundButton}
          isMuted={soundControl.isMuted}
          onSoundToggle={soundControl.toggleSound}
        />

        {/* Toast notifications for offline download status */}
        <ToastContainer
          position='bottom-center'
          autoClose={5000}
          hideProgressBar={false}
          newestOnTop={false}
          closeOnClick
          rtl={false}
          pauseOnFocusLoss
          draggable
          pauseOnHover
          theme='dark'
        />
      </div>
    </>
  );
}

// Layout wrapper for standalone usage
RuntimePresentation.getLayout = function getLayout(page: ReactElement) {
  return <LayoutGuest>{page}</LayoutGuest>;
};
