import {
  mdiContentSave,
  mdiExitToApp,
  mdiImageMultiple,
  mdiMenu,
  mdiPlus,
  mdiSwapHorizontal,
  mdiText,
  mdiTooltipText,
  mdiViewCarousel,
} 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 BaseIcon from '../components/BaseIcon';
import { baseURLApi, getPageTitle } from '../config';
import LayoutAuthenticated from '../layouts/Authenticated';

type TourPage = {
  id: string;
  name?: string;
  slug?: string;
  sort_order?: number;
  environment?: string;
  ui_schema_json?: string;
  background_image_url?: string;
  background_video_url?: string;
  background_audio_url?: string;
  background_loop?: boolean;
};

type ProjectAsset = {
  id: string;
  name?: string;
  asset_type?: 'image' | 'video' | 'audio' | 'file';
  type?: 'icon' | 'background_image' | 'audio' | 'video' | 'transition' | 'logo' | 'favicon' | 'document' | 'general';
  cdn_url?: string | null;
  storage_key?: string | null;
};

type AssetOption = {
  value: string;
  label: string;
};

type CanvasElementType =
  | 'navigation_next'
  | 'navigation_prev'
  | 'gallery'
  | 'carousel'
  | 'tooltip'
  | 'description'
  | 'video_player'
  | 'audio_player';

type CanvasElement = {
  id: string;
  type: CanvasElementType;
  label: string;
  xPercent: number;
  yPercent: number;
  iconUrl?: string;
  galleryCards?: GalleryCard[];
  carouselSlides?: CarouselSlide[];
  carouselPrevIconUrl?: string;
  carouselNextIconUrl?: string;
  tooltipTitle?: string;
  tooltipText?: string;
  descriptionTitle?: string;
  descriptionText?: string;
  navLabel?: string;
  targetPageId?: string;
  transitionVideoUrl?: string;
  transitionReverseMode?: 'auto_reverse' | 'separate_video';
  reverseVideoUrl?: string;
  transitionDurationSec?: number;
  mediaUrl?: string;
  mediaAutoplay?: boolean;
  mediaLoop?: boolean;
  mediaMuted?: boolean;
};

type GalleryCard = {
  id: string;
  imageUrl: string;
  title: string;
  description: string;
};

type CarouselSlide = {
  id: string;
  imageUrl: string;
  caption: string;
};

type ConstructorSchema = {
  elements?: CanvasElement[];
};

type DragElementState = {
  id: string;
  pointerOffsetX: number;
  pointerOffsetY: number;
};

type TransitionPreviewState = {
  videoUrl: string;
  reverseMode: 'none' | 'reverse' | 'separate';
  reverseVideoUrl?: string;
  durationSec?: number;
  title: string;
};

type EditorMenuItem = 'none' | 'background_image' | 'background_video' | 'background_audio' | 'create_transition';

const parseJsonObject = <T,>(value?: string, fallback?: T): T => {
  if (!value) return (fallback || ({} as T)) as T;

  try {
    const parsed = JSON.parse(value);
    return (parsed || fallback || {}) as T;
  } catch (error) {
    console.error('Failed to parse constructor JSON:', error);
    return (fallback || ({} as T)) as T;
  }
};

const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);

const createLocalId = () => {
  if (typeof window !== 'undefined' && window.crypto?.randomUUID) {
    return window.crypto.randomUUID();
  }

  return `constructor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
};

const getAssetLabel = (asset: ProjectAsset) => {
  const baseName = asset.name?.trim() || 'Untitled asset';
  const source = String(asset.storage_key || asset.cdn_url || '').trim();
  return `${baseName}${source ? ` · ${source}` : ''}`;
};

const getAssetSourceValue = (asset: ProjectAsset) => String(asset.storage_key || asset.cdn_url || '').trim();

const extractPrivateUrlFromDownloadPath = (value: string) => {
  const normalized = String(value || '').trim();
  if (!normalized) return '';

  try {
    const parsed = new URL(normalized, 'http://localhost');
    const privateUrl = parsed.searchParams.get('privateUrl');
    return String(privateUrl || '').trim();
  } catch (error) {
    console.error('Failed to parse download URL:', error);
    return '';
  }
};

const extractS3ObjectKey = (value: string) => {
  const normalized = String(value || '').trim();
  if (!normalized) return '';

  try {
    const parsed = new URL(normalized);
    const hostname = String(parsed.hostname || '').toLowerCase();
    if (!hostname.includes('amazonaws.com') && !hostname.includes('cloudfront.net')) return '';
    const decodedPath = decodeURIComponent(String(parsed.pathname || '').replace(/^\/+/, ''));
    if (!decodedPath) return '';

    const pathParts = decodedPath.split('/').filter(Boolean);
    if (pathParts.length <= 1) return decodedPath;

    const firstPart = pathParts[0];
    const isLikelyStoragePrefix = /^[a-f0-9]{24,64}$/i.test(firstPart);
    if (isLikelyStoragePrefix) {
      return pathParts.slice(1).join('/');
    }

    return decodedPath;
  } catch (error) {
    console.error('Failed to parse S3 asset URL:', error);
    return '';
  }
};

const resolveAssetPlaybackUrl = (value?: string) => {
  const normalized = String(value || '').trim();
  if (!normalized) return '';

  if (normalized.startsWith('data:') || normalized.startsWith('blob:')) return normalized;

  if (normalized.startsWith('/api/file/download')) return normalized;

  if (normalized.startsWith('/file/download')) return `${baseURLApi}${normalized}`;

  if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
    const downloadPrivateUrl = extractPrivateUrlFromDownloadPath(normalized);
    if (downloadPrivateUrl) {
      return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(downloadPrivateUrl)}`;
    }

    const s3ObjectKey = extractS3ObjectKey(normalized);
    if (s3ObjectKey) {
      return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(s3ObjectKey)}`;
    }

    return normalized;
  }

  const normalizedPrivateUrl = normalized.replace(/^\/+/, '');
  return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(normalizedPrivateUrl)}`;
};

const isBackgroundImageAsset = (asset: ProjectAsset) => {
  if (asset.type) return asset.type === 'background_image';
  const normalizedName = String(asset.name || '').toLowerCase();
  if (!normalizedName) return false;
  const hasBackgroundKeyword = /\bbackground\b|\bbg\b|backdrop|wallpaper/.test(normalizedName);
  const hasExcludedKeyword = /\bicon\b|\blogo\b/.test(normalizedName);
  return hasBackgroundKeyword && !hasExcludedKeyword;
};

const addFallbackAssetOption = (options: AssetOption[], value?: string, fallbackLabel?: string): AssetOption[] => {
  const normalizedValue = String(value || '').trim();
  if (!normalizedValue) return options;
  if (options.some((option) => option.value === normalizedValue)) return options;
  return [...options, { value: normalizedValue, label: fallbackLabel || `Custom URL · ${normalizedValue}` }];
};

const labelByType: Record<CanvasElementType, string> = {
  navigation_next: 'Navigation: Forward',
  navigation_prev: 'Navigation: Back',
  gallery: 'Gallery',
  carousel: 'Carousel',
  tooltip: 'Tooltip',
  description: 'Description',
  video_player: 'Video Player',
  audio_player: 'Audio Player',
};

const createDefaultElement = (type: CanvasElementType, index: number): CanvasElement => {
  const base: CanvasElement = {
    id: createLocalId(),
    type,
    label: labelByType[type],
    xPercent: clamp(12 + index * 4, 5, 80),
    yPercent: clamp(16 + index * 6, 8, 85),
  };

  if (type === 'gallery') {
    return {
      ...base,
      galleryCards: [{ id: createLocalId(), imageUrl: '', title: 'Card 1', description: '' }],
    };
  }

  if (type === 'carousel') {
    return {
      ...base,
      carouselSlides: [{ id: createLocalId(), imageUrl: '', caption: 'Slide 1' }],
      carouselPrevIconUrl: '',
      carouselNextIconUrl: '',
    };
  }

  if (type === 'tooltip') {
    return {
      ...base,
      iconUrl: '',
      tooltipTitle: 'Tooltip title',
      tooltipText: 'Tooltip text',
    };
  }

  if (type === 'description') {
    return {
      ...base,
      iconUrl: '',
      descriptionTitle: 'Description title',
      descriptionText: 'Description text',
    };
  }

  if (type === 'navigation_next' || type === 'navigation_prev') {
    return {
      ...base,
      navLabel: type === 'navigation_next' ? 'Forward' : 'Back',
      iconUrl: '',
      transitionReverseMode: 'auto_reverse',
      transitionDurationSec: 0.7,
    };
  }

  if (type === 'video_player' || type === 'audio_player') {
    return {
      ...base,
      mediaUrl: '',
      mediaAutoplay: true,
      mediaLoop: true,
      mediaMuted: type === 'video_player',
    };
  }

  return base;
};

const getElementButtonTitle = (element: CanvasElement) => {
  if (element.type === 'gallery') {
    return `${element.label} (${element.galleryCards?.length || 0})`;
  }

  if (element.type === 'carousel') {
    return `${element.label} (${element.carouselSlides?.length || 0})`;
  }

  if (element.type === 'tooltip' && element.tooltipTitle) return element.tooltipTitle;
  if (element.type === 'description' && element.descriptionTitle) return element.descriptionTitle;
  if ((element.type === 'navigation_next' || element.type === 'navigation_prev') && element.navLabel) return element.navLabel;
  if ((element.type === 'video_player' || element.type === 'audio_player') && element.mediaUrl) {
    return `${element.label} · configured`;
  }

  return element.label;
};

const ConstructorPage = () => {
  const router = useRouter();
  const canvasRef = useRef<HTMLDivElement>(null);
  const elementEditorRef = useRef<HTMLDivElement>(null);
  const [isAuthReady, setIsAuthReady] = useState(false);

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

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

  const [pages, setPages] = useState<TourPage[]>([]);
  const [assets, setAssets] = useState<ProjectAsset[]>([]);
  const [activePageId, setActivePageId] = useState('');
  const [projectName, setProjectName] = useState('');

  const [elements, setElements] = useState<CanvasElement[]>([]);
  const [backgroundImageUrl, setBackgroundImageUrl] = useState('');
  const [backgroundVideoUrl, setBackgroundVideoUrl] = useState('');
  const [backgroundAudioUrl, setBackgroundAudioUrl] = useState('');
  const [selectedElementId, setSelectedElementId] = useState('');
  const [selectedMenuItem, setSelectedMenuItem] = useState<EditorMenuItem>('none');
  const [transitionPreview, setTransitionPreview] = useState<TransitionPreviewState | null>(null);

  const [isLoading, setIsLoading] = useState(true);
  const [isSaving, setIsSaving] = useState(false);
  const [isCreatingPage, setIsCreatingPage] = useState(false);
  const [isCreatingTransition, setIsCreatingTransition] = useState(false);
  const [newTransitionName, setNewTransitionName] = useState('');
  const [newTransitionVideoUrl, setNewTransitionVideoUrl] = useState('');
  const [newTransitionSupportsReverse, setNewTransitionSupportsReverse] = useState(true);
  const [newTransitionDurationSec, setNewTransitionDurationSec] = useState(0.7);
  const [errorMessage, setErrorMessage] = useState('');
  const [successMessage, setSuccessMessage] = useState('');

  const [menuPosition, setMenuPosition] = useState({ x: 0, y: 110 });
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const [editorPosition, setEditorPosition] = useState({ x: 0, y: 72 });
  const [isEditorCollapsed, setIsEditorCollapsed] = useState(false);

  const menuDragRef = useRef<{ pointerOffsetX: number; pointerOffsetY: number } | null>(null);
  const editorDragRef = useRef<{ pointerOffsetX: number; pointerOffsetY: number } | null>(null);
  const elementDragRef = useRef<DragElementState | null>(null);
  const transitionVideoRef = useRef<HTMLVideoElement | null>(null);
  const reverseAnimationFrame = useRef<number | null>(null);
  const didSetInitialCanvasFocus = useRef(false);

  const activePage = useMemo(() => pages.find((item) => item.id === activePageId) || null, [activePageId, pages]);
  const pageNameById = useMemo(() => {
    const acc: Record<string, string> = {};
    pages.forEach((page, index) => {
      acc[String(page.id)] = page.name || `Page ${index + 1}`;
    });
    return acc;
  }, [pages]);
  const selectedElement = useMemo(
    () => elements.find((element) => element.id === selectedElementId) || null,
    [elements, selectedElementId],
  );
  const imageAssetOptions = useMemo(
    () =>
      assets
        .filter((asset) => asset.asset_type === 'image' && getAssetSourceValue(asset))
        .map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
    [assets],
  );
  const backgroundImageAssetOptions = useMemo(
    () =>
      assets
        .filter((asset) => asset.asset_type === 'image' && getAssetSourceValue(asset) && isBackgroundImageAsset(asset))
        .map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
    [assets],
  );
  const videoAssetOptions = useMemo(
    () =>
      assets
        .filter((asset) => asset.asset_type === 'video' && getAssetSourceValue(asset))
        .map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
    [assets],
  );
  const audioAssetOptions = useMemo(
    () =>
      assets
        .filter((asset) => asset.asset_type === 'audio' && getAssetSourceValue(asset))
        .map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
    [assets],
  );
  const transitionVideoAssetOptions = useMemo(() => {
    const typedAssets = assets
      .filter((asset) => asset.type === 'transition' && asset.asset_type === 'video' && getAssetSourceValue(asset))
      .map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) }));

    if (typedAssets.length > 0) return typedAssets;

    const taggedAssets = assets
      .filter(
        (asset) => asset.asset_type === 'video' && getAssetSourceValue(asset) && /\[TRANSITION\]/i.test(String(asset.name || '')),
      )
      .map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) }));

    if (taggedAssets.length > 0) return taggedAssets;

    return videoAssetOptions;
  }, [assets, videoAssetOptions]);
  const iconAssetOptions = useMemo(
    () =>
      assets
        .filter((asset) => asset.type === 'icon' && asset.asset_type === 'image' && getAssetSourceValue(asset))
        .map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
    [assets],
  );

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

  const loadData = useCallback(async () => {
    if (!projectId || !router.isReady || !isAuthReady) return;

    try {
      setIsLoading(true);
      setErrorMessage('');
      setSuccessMessage('');

      const [projectResponse, pagesResponse, assetsResponse] = await Promise.all([
        axios.get(`/projects/${projectId}`),
        axios.get(`/tour_pages?limit=500&sort=asc&field=sort_order&project=${projectId}`),
        axios.get(`/assets?limit=500&page=0&sort=desc&field=createdAt&project=${projectId}`),
      ]);

      const pageRows: TourPage[] = Array.isArray(pagesResponse?.data?.rows) ? pagesResponse.data.rows : [];
      const assetRows: ProjectAsset[] = Array.isArray(assetsResponse?.data?.rows) ? assetsResponse.data.rows : [];
      setProjectName(projectResponse?.data?.name || '');
      setPages(pageRows);
      setAssets(assetRows);

      const defaultPageId = pageIdFromRoute || pageRows[0]?.id || '';
      setActivePageId(defaultPageId);
      setIsMenuOpen(false);
    } catch (error: any) {
      if (error?.response?.status === 401) {
        const message = 'Your session has expired. Please sign in again.';
        console.error('Unauthorized constructor request:', error);
        setErrorMessage(message);
        setPages([]);
        setAssets([]);
        router.replace('/login');
        return;
      }

      const message = error?.response?.data?.message || error?.message || 'Failed to load constructor data.';
      console.error('Failed to load constructor data:', error);
      setErrorMessage(message);
      setPages([]);
      setAssets([]);
    } finally {
      setIsLoading(false);
    }
  }, [isAuthReady, pageIdFromRoute, projectId, router]);

  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('/projects/projects-list');
  }, [projectId, router]);

  useEffect(() => {
    loadData();
  }, [loadData]);

  useEffect(() => {
    if (typeof window === 'undefined') return;

    setMenuPosition((prev) => {
      if (prev.x > 0) return prev;
      return {
        x: Math.max(window.innerWidth - 280, 20),
        y: prev.y,
      };
    });

    setEditorPosition((prev) => {
      if (prev.x > 0) return prev;
      return {
        x: Math.max(window.innerWidth - 400, 20),
        y: prev.y,
      };
    });
  }, []);

  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([]);
      setSelectedElementId('');
      setBackgroundImageUrl('');
      setBackgroundVideoUrl('');
      setBackgroundAudioUrl('');
      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) => ({
            ...item,
            id: String(item.id || createLocalId()),
            label: labelByType[item.type as CanvasElementType],
            xPercent: clamp(Number(item.xPercent || 0), 0, 100),
            yPercent: clamp(Number(item.yPercent || 0), 0, 100),
            galleryCards: Array.isArray(item.galleryCards)
              ? item.galleryCards.map((card: any, index: number) => ({
                  id: String(card?.id || createLocalId()),
                  imageUrl: String(card?.imageUrl || ''),
                  title: String(card?.title || `Card ${index + 1}`),
                  description: String(card?.description || ''),
                }))
              : undefined,
            carouselSlides: Array.isArray(item.carouselSlides)
              ? item.carouselSlides.map((slide: any, index: number) => ({
                  id: String(slide?.id || createLocalId()),
                  imageUrl: String(slide?.imageUrl || ''),
                  caption: String(slide?.caption || `Slide ${index + 1}`),
                }))
              : undefined,
            iconUrl: typeof item.iconUrl === 'string' ? item.iconUrl : '',
            carouselPrevIconUrl: typeof item.carouselPrevIconUrl === 'string' ? item.carouselPrevIconUrl : '',
            carouselNextIconUrl: typeof item.carouselNextIconUrl === 'string' ? item.carouselNextIconUrl : '',
            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 : '',
            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 : item.type === 'video_player',
          }))
      : [];

    setElements(normalizedElements);
    setSelectedMenuItem('none');
    setSelectedElementId((current) => {
      if (!normalizedElements.length) return '';
      if (normalizedElements.some((element) => element.id === current)) return current;
      return '';
    });
    setBackgroundImageUrl(activePage.background_image_url || '');
    setBackgroundVideoUrl(activePage.background_video_url || '');
    setBackgroundAudioUrl(activePage.background_audio_url || '');
  }, [activePage]);

  useEffect(() => {
    const onPointerMove = (event: MouseEvent) => {
      if (menuDragRef.current) {
        const nextX = clamp(event.clientX - menuDragRef.current.pointerOffsetX, 0, window.innerWidth - 240);
        const nextY = clamp(event.clientY - menuDragRef.current.pointerOffsetY, 0, window.innerHeight - 60);
        setMenuPosition({ x: nextX, y: nextY });
        return;
      }

      if (editorDragRef.current) {
        const editorWidth = isEditorCollapsed ? 260 : 380;
        const nextX = clamp(event.clientX - editorDragRef.current.pointerOffsetX, 0, window.innerWidth - editorWidth);
        const nextY = clamp(event.clientY - editorDragRef.current.pointerOffsetY, 0, window.innerHeight - 60);
        setEditorPosition({ x: nextX, y: nextY });
        return;
      }

      if (!elementDragRef.current || !canvasRef.current) return;

      const rect = canvasRef.current.getBoundingClientRect();
      const rawX = event.clientX - rect.left - elementDragRef.current.pointerOffsetX;
      const rawY = event.clientY - rect.top - elementDragRef.current.pointerOffsetY;
      const nextXPercent = clamp((rawX / rect.width) * 100, 0, 100);
      const nextYPercent = clamp((rawY / rect.height) * 100, 0, 100);

      setElements((prev) =>
        prev.map((item) =>
          item.id === elementDragRef.current?.id ? { ...item, xPercent: nextXPercent, yPercent: nextYPercent } : item,
        ),
      );
    };

    const onPointerUp = () => {
      menuDragRef.current = null;
      editorDragRef.current = null;
      elementDragRef.current = null;
    };

    window.addEventListener('mousemove', onPointerMove);
    window.addEventListener('mouseup', onPointerUp);

    return () => {
      window.removeEventListener('mousemove', onPointerMove);
      window.removeEventListener('mouseup', onPointerUp);
    };
  }, [isEditorCollapsed]);

  useEffect(() => {
    if (!selectedElementId && selectedMenuItem === 'none') return;

    const onOutsideMouseDown = (event: MouseEvent) => {
      const target = event.target as HTMLElement | null;
      if (!target) return;
      if (elementEditorRef.current?.contains(target)) return;
      const clickedElementId = target.closest('[data-constructor-element-id]')?.getAttribute('data-constructor-element-id');
      if (selectedElementId && clickedElementId && clickedElementId === selectedElementId) return;
      setSelectedElementId('');
      setSelectedMenuItem('none');
    };

    window.addEventListener('mousedown', onOutsideMouseDown);
    return () => window.removeEventListener('mousedown', onOutsideMouseDown);
  }, [selectedElementId, selectedMenuItem]);

  const selectElementForEdit = (elementId: string) => {
    setSelectedElementId(elementId);
    setSelectedMenuItem('none');
  };

  const selectMenuItemForEdit = (item: EditorMenuItem) => {
    setSelectedElementId('');
    setSelectedMenuItem(item);
  };

  const addElement = (type: CanvasElementType) => {
    const nextElement = createDefaultElement(type, elements.length);
    setElements((prev) => [...prev, nextElement]);
    selectElementForEdit(nextElement.id);
    setSuccessMessage('Element added. Drag it to set position.');
    setErrorMessage('');
  };

  const createPage = useCallback(async () => {
    if (!projectId) {
      setErrorMessage('Project is required.');
      return;
    }

    const maxSortOrder = Math.max(0, ...pages.map((item) => Number(item.sort_order || 0)));
    const nextPageNumber = pages.length + 1;

    const payload = {
      project: projectId,
      environment: activePage?.environment || 'dev',
      source_key: '',
      name: `Page ${nextPageNumber}`,
      slug: `page-${nextPageNumber}-${Date.now().toString().slice(-4)}`,
      sort_order: maxSortOrder + 1,
      background_image_url: '',
      background_video_url: '',
      background_audio_url: '',
      background_loop: false,
      requires_auth: false,
      ui_schema_json: JSON.stringify({ elements: [] }),
    };

    try {
      setIsCreatingPage(true);
      setErrorMessage('');
      setSuccessMessage('');
      const response = await axios.post('/tour_pages', { data: payload });
      const createdPage = response?.data;

      await loadData();

      if (createdPage?.id) {
        setActivePageId(createdPage.id);
      }

      setIsMenuOpen(true);
      setSuccessMessage('New page created. You can now configure it in constructor.');
    } catch (error: any) {
      const message = error?.response?.data?.message || error?.message || 'Failed to create page.';
      console.error('Failed to create page from constructor:', error);
      setErrorMessage(message);
    } finally {
      setIsCreatingPage(false);
    }
  }, [activePage?.environment, loadData, pages, projectId]);

  const createTransition = useCallback(async () => {
    if (!projectId) {
      setErrorMessage('Project is required.');
      return;
    }

    const sanitizedVideoUrl = String(newTransitionVideoUrl || '').trim();
    if (!sanitizedVideoUrl) {
      setErrorMessage('Select a transition video asset first.');
      return;
    }

    const sanitizedName = String(newTransitionName || '').trim() || `Transition ${Date.now().toString().slice(-4)}`;
    const parsedDuration = Number(newTransitionDurationSec);

    try {
      setIsCreatingTransition(true);
      setErrorMessage('');
      setSuccessMessage('');

      const payload = {
        project: projectId,
        environment: activePage?.environment || 'dev',
        source_key: '',
        name: sanitizedName,
        slug: `transition-${createLocalId()}`,
        video_url: sanitizedVideoUrl,
        audio_url: '',
        supports_reverse: Boolean(newTransitionSupportsReverse),
        duration_sec: Number.isFinite(parsedDuration) && parsedDuration > 0 ? parsedDuration : 0.7,
      };

      await axios.post('/transitions', { data: payload });
      setSuccessMessage('Transition created.');
      setNewTransitionName('');
    } catch (error: any) {
      const message = error?.response?.data?.message || error?.message || 'Failed to create transition.';
      console.error('Failed to create transition from constructor:', error);
      setErrorMessage(message);
    } finally {
      setIsCreatingTransition(false);
    }
  }, [activePage?.environment, newTransitionDurationSec, newTransitionName, newTransitionSupportsReverse, newTransitionVideoUrl, projectId]);

  const saveConstructor = useCallback(async () => {
    if (!activePageId) {
      setErrorMessage('Select a page before saving.');
      return;
    }

    try {
      setIsSaving(true);
      setErrorMessage('');
      setSuccessMessage('');

      const existingSchema = parseJsonObject<Record<string, any>>(activePage?.ui_schema_json, {});
      const schemaToSave = {
        ...existingSchema,
        elements,
      };

      await axios.put(`/tour_pages/${activePageId}`, {
        id: activePageId,
        data: {
          ui_schema_json: JSON.stringify(schemaToSave),
          background_image_url: backgroundImageUrl,
          background_video_url: backgroundVideoUrl,
          background_audio_url: backgroundAudioUrl,
          background_loop: Boolean(backgroundAudioUrl),
        },
      });

      setSuccessMessage('Constructor settings saved. Element positions are stored in percentages.');
      await loadData();
    } catch (error: any) {
      const message = error?.response?.data?.message || error?.message || 'Failed to save constructor changes.';
      console.error('Failed to save constructor changes:', error);
      setErrorMessage(message);
    } finally {
      setIsSaving(false);
    }
  }, [activePage?.ui_schema_json, activePageId, backgroundAudioUrl, backgroundImageUrl, backgroundVideoUrl, elements, loadData]);

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

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

    const rect = canvasRef.current.getBoundingClientRect();
    const elementLeftPx = (currentElement.xPercent / 100) * rect.width;
    const elementTopPx = (currentElement.yPercent / 100) * rect.height;

    elementDragRef.current = {
      id: elementId,
      pointerOffsetX: event.clientX - rect.left - elementLeftPx,
      pointerOffsetY: event.clientY - rect.top - elementTopPx,
    };
  };

  const updateSelectedElement = (patch: Partial<CanvasElement>) => {
    if (!selectedElementId) return;
    setElements((prev) => prev.map((item) => (item.id === selectedElementId ? { ...item, ...patch } : item)));
  };

  const removeSelectedElement = () => {
    if (!selectedElementId) return;

    let nextSelectedId = '';
    setElements((prev) => {
      const filtered = prev.filter((item) => item.id !== selectedElementId);
      nextSelectedId = filtered[0]?.id || '';
      return filtered;
    });
    if (nextSelectedId) {
      selectElementForEdit(nextSelectedId);
    } else {
      setSelectedElementId('');
      setSelectedMenuItem('none');
    }
    setSuccessMessage('Element removed.');
  };

  const updateGalleryCard = (cardId: string, patch: Partial<GalleryCard>) => {
    if (!selectedElement || selectedElement.type !== 'gallery') return;
    const nextCards = (selectedElement.galleryCards || []).map((card) => (card.id === cardId ? { ...card, ...patch } : card));
    updateSelectedElement({ galleryCards: nextCards });
  };

  const addGalleryCard = () => {
    if (!selectedElement || selectedElement.type !== 'gallery') return;
    const nextCards = [
      ...(selectedElement.galleryCards || []),
      { id: createLocalId(), imageUrl: '', title: `Card ${(selectedElement.galleryCards || []).length + 1}`, description: '' },
    ];
    updateSelectedElement({ galleryCards: nextCards });
  };

  const removeGalleryCard = (cardId: string) => {
    if (!selectedElement || selectedElement.type !== 'gallery') return;
    const nextCards = (selectedElement.galleryCards || []).filter((card) => card.id !== cardId);
    updateSelectedElement({ galleryCards: nextCards });
  };

  const updateCarouselSlide = (slideId: string, patch: Partial<CarouselSlide>) => {
    if (!selectedElement || selectedElement.type !== 'carousel') return;
    const nextSlides = (selectedElement.carouselSlides || []).map((slide) =>
      slide.id === slideId ? { ...slide, ...patch } : slide,
    );
    updateSelectedElement({ carouselSlides: nextSlides });
  };

  const addCarouselSlide = () => {
    if (!selectedElement || selectedElement.type !== 'carousel') return;
    const nextSlides = [
      ...(selectedElement.carouselSlides || []),
      { id: createLocalId(), imageUrl: '', caption: `Slide ${(selectedElement.carouselSlides || []).length + 1}` },
    ];
    updateSelectedElement({ carouselSlides: nextSlides });
  };

  const removeCarouselSlide = (slideId: string) => {
    if (!selectedElement || selectedElement.type !== 'carousel') return;
    const nextSlides = (selectedElement.carouselSlides || []).filter((slide) => slide.id !== slideId);
    updateSelectedElement({ carouselSlides: nextSlides });
  };

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

    if (!selectedElement.transitionVideoUrl) {
      setErrorMessage('Select transition video asset to preview transition playback.');
      return;
    }

    if (direction === 'back' && selectedElement.transitionReverseMode === 'separate_video' && !selectedElement.reverseVideoUrl) {
      setErrorMessage('Select back-transition asset or switch reverse mode to Auto Reverse.');
      return;
    }

    setTransitionPreview({
      videoUrl: selectedElement.transitionVideoUrl,
      reverseMode:
        direction === 'forward'
          ? 'none'
          : selectedElement.transitionReverseMode === 'separate_video'
            ? 'separate'
            : 'reverse',
      reverseVideoUrl: selectedElement.reverseVideoUrl,
      durationSec: selectedElement.transitionDurationSec,
      title: `${selectedElement.navLabel || selectedElement.label} · ${direction}`,
    });
  };

  const onMenuDragStart = (event: React.MouseEvent) => {
    const targetRect = (event.currentTarget as HTMLElement).getBoundingClientRect();
    menuDragRef.current = {
      pointerOffsetX: event.clientX - targetRect.left,
      pointerOffsetY: event.clientY - targetRect.top,
    };
  };

  const onElementEditorDragStart = (event: React.MouseEvent) => {
    const target = event.target as HTMLElement;
    if (target.closest('button')) return;

    const targetRect = (event.currentTarget as HTMLElement).getBoundingClientRect();
    editorDragRef.current = {
      pointerOffsetX: event.clientX - targetRect.left,
      pointerOffsetY: event.clientY - targetRect.top,
    };
  };

  const renderCanvasElementContent = (element: CanvasElement) => {
    if (element.type === 'navigation_next' || element.type === 'navigation_prev') {
      const targetPageName = element.targetPageId ? pageNameById[element.targetPageId] : '';
      return (
        <div className='flex flex-col items-start gap-1'>
          <div className='flex items-center gap-2'>
            {element.iconUrl ? (
              // eslint-disable-next-line @next/next/no-img-element
              <img src={resolveAssetPlaybackUrl(element.iconUrl)} alt='Navigation icon' className='h-4 w-4 object-contain' />
            ) : null}
            <span>{element.navLabel || (element.type === 'navigation_next' ? 'Forward' : 'Back')}</span>
          </div>
          {targetPageName ? <span className='text-[10px] text-gray-500'>To: {targetPageName}</span> : null}
        </div>
      );
    }

    if (element.type === 'tooltip') {
      return (
        <div className='max-w-[200px] text-left'>
          {element.iconUrl ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={resolveAssetPlaybackUrl(element.iconUrl)} alt='Tooltip icon' className='mb-1 h-5 w-5 object-contain' />
          ) : null}
          <p className='text-[11px] font-bold'>{element.tooltipTitle || 'Tooltip title'}</p>
          <p className='text-[10px] text-gray-600 line-clamp-3'>{element.tooltipText || 'Tooltip text'}</p>
        </div>
      );
    }

    if (element.type === 'description') {
      return (
        <div className='max-w-[220px] text-left'>
          {element.iconUrl ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={resolveAssetPlaybackUrl(element.iconUrl)} alt='Description icon' className='mb-1 h-5 w-5 object-contain' />
          ) : null}
          <p className='text-[11px] font-bold'>{element.descriptionTitle || 'Description title'}</p>
          <p className='text-[10px] text-gray-600 line-clamp-4'>{element.descriptionText || 'Description text'}</p>
        </div>
      );
    }

    if (element.type === 'gallery') {
      const cards = element.galleryCards || [];
      return (
        <div className='w-[220px]'>
          <p className='mb-1 text-left text-[10px] font-semibold text-gray-600'>Gallery ({cards.length})</p>
          <div className='grid grid-cols-3 gap-1'>
            {cards.slice(0, 6).map((card) => (
              <div key={card.id} className='h-12 overflow-hidden rounded bg-gray-100'>
                {card.imageUrl ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img
                    src={resolveAssetPlaybackUrl(card.imageUrl)}
                    alt={card.title || 'Gallery card'}
                    className='h-full w-full object-cover'
                  />
                ) : (
                  <div className='flex h-full items-center justify-center text-[9px] text-gray-400'>No image</div>
                )}
              </div>
            ))}
          </div>
        </div>
      );
    }

    if (element.type === 'carousel') {
      const firstSlide = (element.carouselSlides || [])[0];
      return (
        <div className='w-[220px] text-left'>
          <p className='mb-1 text-[10px] font-semibold text-gray-600'>Carousel ({element.carouselSlides?.length || 0})</p>
          <div className='h-20 overflow-hidden rounded bg-gray-100'>
            {firstSlide?.imageUrl ? (
              // eslint-disable-next-line @next/next/no-img-element
              <img
                src={resolveAssetPlaybackUrl(firstSlide.imageUrl)}
                alt={firstSlide.caption || 'Carousel slide'}
                className='h-full w-full object-cover'
              />
            ) : (
              <div className='flex h-full items-center justify-center text-[10px] text-gray-400'>No slide image</div>
            )}
          </div>
          <p className='mt-1 text-[10px] text-gray-600 line-clamp-1'>{firstSlide?.caption || 'No caption'}</p>
          {(element.carouselPrevIconUrl || element.carouselNextIconUrl) && (
            <div className='mt-1 flex items-center justify-between text-[9px] text-gray-500'>
              <span className='flex items-center gap-1'>
                {element.carouselPrevIconUrl ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img
                    src={resolveAssetPlaybackUrl(element.carouselPrevIconUrl)}
                    alt='Previous icon'
                    className='h-3 w-3 object-contain'
                  />
                ) : null}
                Prev
              </span>
              <span className='flex items-center gap-1'>
                Next
                {element.carouselNextIconUrl ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img
                    src={resolveAssetPlaybackUrl(element.carouselNextIconUrl)}
                    alt='Next icon'
                    className='h-3 w-3 object-contain'
                  />
                ) : null}
              </span>
            </div>
          )}
        </div>
      );
    }

    if (element.type === 'video_player') {
      return (
        <div className='w-[220px] text-left'>
          <p className='mb-1 text-[10px] font-semibold text-gray-600'>Video player</p>
          <video
            key={`${element.id}_${element.mediaUrl || ''}_${String(Boolean(element.mediaAutoplay))}_${String(Boolean(element.mediaLoop))}_${String(Boolean(element.mediaMuted))}`}
            className='h-24 w-full rounded bg-black object-cover'
            src={resolveAssetPlaybackUrl(element.mediaUrl)}
            controls
            autoPlay={Boolean(element.mediaAutoplay)}
            loop={Boolean(element.mediaLoop)}
            muted={Boolean(element.mediaMuted)}
            playsInline
          />
        </div>
      );
    }

    if (element.type === 'audio_player') {
      return (
        <div className='w-[240px] text-left'>
          <p className='mb-1 text-[10px] font-semibold text-gray-600'>Audio player</p>
          <audio
            key={`${element.id}_${element.mediaUrl || ''}_${String(Boolean(element.mediaAutoplay))}_${String(Boolean(element.mediaLoop))}`}
            className='w-full'
            src={resolveAssetPlaybackUrl(element.mediaUrl)}
            controls
            autoPlay={Boolean(element.mediaAutoplay)}
            loop={Boolean(element.mediaLoop)}
          />
        </div>
      );
    }

    return getElementButtonTitle(element);
  };

  const canvasBackgroundStyle: React.CSSProperties = {};
  const backgroundImageSrc = resolveAssetPlaybackUrl(backgroundImageUrl);
  const backgroundVideoSrc = resolveAssetPlaybackUrl(backgroundVideoUrl);
  const backgroundAudioSrc = resolveAssetPlaybackUrl(backgroundAudioUrl);
  const backgroundImageSelectOptions = addFallbackAssetOption(
    backgroundImageAssetOptions,
    backgroundImageUrl,
    `Current image · ${backgroundImageUrl}`,
  );
  const backgroundVideoSelectOptions = addFallbackAssetOption(videoAssetOptions, backgroundVideoUrl, `Current video · ${backgroundVideoUrl}`);
  const backgroundAudioSelectOptions = addFallbackAssetOption(audioAssetOptions, backgroundAudioUrl, `Current audio · ${backgroundAudioUrl}`);
  const hasEditorSelection = 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';

  if (backgroundImageSrc) {
    canvasBackgroundStyle.backgroundImage = `url("${backgroundImageSrc}")`;
    canvasBackgroundStyle.backgroundSize = 'cover';
    canvasBackgroundStyle.backgroundPosition = 'center';
  }

  useEffect(() => {
    const video = transitionVideoRef.current;
    if (!transitionPreview || !video) return;

    let fallbackTimer: ReturnType<typeof setTimeout> | null = null;

    const cleanupReverseFrame = () => {
      if (reverseAnimationFrame.current !== null) {
        cancelAnimationFrame(reverseAnimationFrame.current);
        reverseAnimationFrame.current = null;
      }
    };

    const finishPreview = () => {
      cleanupReverseFrame();
      setTransitionPreview(null);
    };

    const configuredDurationMs = (transitionPreview.durationSec && transitionPreview.durationSec > 0 ? transitionPreview.durationSec : 0.7) * 1000;

    const runReversePreview = () => {
      cleanupReverseFrame();
      const duration = Number.isFinite(video.duration) && video.duration > 0 ? video.duration : Math.max(configuredDurationMs / 1000, 0.7);
      const reverseSeconds = transitionPreview.durationSec && transitionPreview.durationSec > 0 ? transitionPreview.durationSec : duration;
      const reverseMs = Math.max(reverseSeconds * 1000, 400);
      const reverseRate = duration / reverseSeconds;
      const startTime = performance.now();
      video.pause();
      video.currentTime = duration;

      const step = (now: number) => {
        const elapsed = now - startTime;
        const nextTime = Math.max(duration - (elapsed / 1000) * reverseRate, 0);
        video.currentTime = nextTime;

        if (elapsed >= reverseMs || nextTime <= 0.001) {
          finishPreview();
          return;
        }

        reverseAnimationFrame.current = requestAnimationFrame(step);
      };

      reverseAnimationFrame.current = requestAnimationFrame(step);
    };

    const onLoadedMetadata = () => {
      if (transitionPreview.reverseMode === 'reverse') {
        runReversePreview();
        return;
      }

      video.currentTime = 0;
      video.play().catch((playError) => {
        console.error('Transition preview playback failed:', playError);
        fallbackTimer = setTimeout(finishPreview, configuredDurationMs);
      });
    };

    const onEnded = () => finishPreview();

    video.addEventListener('loadedmetadata', onLoadedMetadata);
    video.addEventListener('ended', onEnded);
    fallbackTimer = setTimeout(finishPreview, configuredDurationMs + 500);

    return () => {
      video.removeEventListener('loadedmetadata', onLoadedMetadata);
      video.removeEventListener('ended', onEnded);
      cleanupReverseFrame();
      if (fallbackTimer) clearTimeout(fallbackTimer);
    };
  }, [transitionPreview]);

  useEffect(() => {
    return () => {
      if (reverseAnimationFrame.current !== null) {
        cancelAnimationFrame(reverseAnimationFrame.current);
      }
    };
  }, []);

  return (
    <>
      <Head>
        <title>{getPageTitle('Constructor')}</title>
      </Head>
      <div className='relative w-screen h-screen bg-white overflow-hidden'>
        <div className='absolute top-4 left-4 z-30 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 && (
            <div className='flex items-center gap-2'>
              <select
                className='border border-gray-300 rounded px-3 py-2 bg-white text-sm'
                value={activePageId}
                onChange={(event) => setActivePageId(event.target.value)}
              >
                {pages.map((page, index) => (
                  <option key={page.id} value={page.id}>
                    {page.name || `Page ${index + 1}`}
                  </option>
                ))}
              </select>
              <BaseButton
                color='lightDark'
                label='Exit to Assets'
                icon={mdiExitToApp}
                href={projectId ? `/projects/${projectId}` : '/projects/projects-list'}
              />
            </div>
          )}
        </div>

        <div ref={canvasRef} tabIndex={-1} className='absolute inset-0 bg-white overflow-hidden' style={canvasBackgroundStyle}>
          {backgroundImageSrc ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img
              key={`bg_image_${backgroundImageSrc}`}
              src={backgroundImageSrc}
              alt='Background'
              className='absolute inset-0 h-full w-full object-cover pointer-events-none select-none'
            />
          ) : null}

          {backgroundVideoSrc ? (
            <video
              key={`bg_video_${backgroundVideoSrc}`}
              className='absolute inset-0 w-full h-full object-cover'
              src={backgroundVideoSrc}
              autoPlay
              loop
              muted
              playsInline
            />
          ) : null}

          {backgroundAudioSrc ? <audio key={`bg_audio_${backgroundAudioSrc}`} src={backgroundAudioSrc} autoPlay loop hidden /> : null}

          {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) => (
              <button
                key={element.id}
                type='button'
                data-constructor-element-id={element.id}
                className={`absolute border rounded px-3 py-2 text-xs font-semibold shadow cursor-move text-left ${
                  selectedElementId === element.id ? 'border-blue-500 bg-blue-50' : 'border-blue-200 bg-white/95'
                }`}
                style={{ left: `${element.xPercent}%`, top: `${element.yPercent}%`, transform: 'translate(-50%, -50%)' }}
                onMouseDown={(event) => onElementMouseDown(event, element.id)}
                onClick={() => selectElementForEdit(element.id)}
              >
                {renderCanvasElementContent(element)}
              </button>
            ))
          )}
        </div>

        {pages.length > 0 && hasEditorSelection && (
          <div
            ref={elementEditorRef}
            className={`fixed z-40 ${isEditorCollapsed ? 'w-[260px]' : 'w-[380px]'} max-h-[calc(100vh-2rem)] overflow-auto rounded-lg border border-gray-200 bg-white/95 p-3 shadow-xl`}
            style={{ left: editorPosition.x, top: editorPosition.y }}
          >
            <div className='mb-3 flex items-center justify-between gap-2 cursor-move' onMouseDown={onElementEditorDragStart}>
              <p className='text-xs font-bold uppercase tracking-wide text-gray-700'>{editorTitle}</p>
              <div className='flex items-center gap-2'>
                <button
                  type='button'
                  className='text-xs text-gray-700 hover:underline'
                  onClick={() => setIsEditorCollapsed((prev) => !prev)}
                >
                  {isEditorCollapsed ? 'Expand' : 'Collapse'}
                </button>
                {selectedElement && (
                  <button type='button' className='text-xs text-red-600 hover:underline' onClick={removeSelectedElement}>
                    Remove element
                  </button>
                )}
              </div>
            </div>

            {!isEditorCollapsed && (
              <>
                {selectedMenuItem === 'background_image' && (
                  <div>
                    <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Background image</label>
                    <select
                      className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                      value={backgroundImageUrl}
                      onChange={(event) => {
                        const nextValue = event.target.value;
                        setBackgroundImageUrl(nextValue);
                        if (nextValue) setBackgroundVideoUrl('');
                      }}
                    >
                      <option value=''>None</option>
                      {backgroundImageSelectOptions.map((option) => (
                        <option key={option.value} value={option.value}>
                          {option.label}
                        </option>
                      ))}
                    </select>
                  </div>
                )}

                {selectedMenuItem === 'background_video' && (
                  <div>
                    <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Background video</label>
                    <select
                      className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                      value={backgroundVideoUrl}
                      onChange={(event) => {
                        const nextValue = event.target.value;
                        setBackgroundVideoUrl(nextValue);
                        if (nextValue) setBackgroundImageUrl('');
                      }}
                    >
                      <option value=''>None</option>
                      {backgroundVideoSelectOptions.map((option) => (
                        <option key={option.value} value={option.value}>
                          {option.label}
                        </option>
                      ))}
                    </select>
                  </div>
                )}

                {selectedMenuItem === 'background_audio' && (
                  <div>
                    <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Background audio (loop)</label>
                    <select
                      className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                      value={backgroundAudioUrl}
                      onChange={(event) => setBackgroundAudioUrl(event.target.value)}
                    >
                      <option value=''>None</option>
                      {backgroundAudioSelectOptions.map((option) => (
                        <option key={option.value} value={option.value}>
                          {option.label}
                        </option>
                      ))}
                    </select>
                  </div>
                )}

                {selectedMenuItem === 'create_transition' && (
                  <div className='rounded border border-gray-200 p-2 space-y-2'>
                    <p className='text-[11px] font-semibold text-gray-600'>Create next page transition</p>
                    <input
                      className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                      placeholder='Name'
                      value={newTransitionName}
                      onChange={(event) => setNewTransitionName(event.target.value)}
                    />
                    <select
                      className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                      value={newTransitionVideoUrl}
                      onChange={(event) => setNewTransitionVideoUrl(event.target.value)}
                    >
                      <option value=''>Transition video asset</option>
                      {transitionVideoAssetOptions.map((option) => (
                        <option key={option.value} value={option.value}>
                          {option.label}
                        </option>
                      ))}
                    </select>
                    <label className='flex items-center gap-2 text-[11px] text-gray-700'>
                      <input
                        type='checkbox'
                        checked={newTransitionSupportsReverse}
                        onChange={(event) => setNewTransitionSupportsReverse(event.target.checked)}
                      />
                      Supports reverse playback
                    </label>
                    <input
                      className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                      type='number'
                      min='0.2'
                      step='0.1'
                      value={newTransitionDurationSec}
                      onChange={(event) => setNewTransitionDurationSec(Number(event.target.value || 0.7))}
                    />
                    <button
                      type='button'
                      className='menu-action-btn'
                      onClick={createTransition}
                      disabled={isCreatingTransition}
                    >
                      <BaseIcon path={mdiSwapHorizontal} size={16} />
                      <span>{isCreatingTransition ? 'Creating Transition...' : 'Create Transition'}</span>
                    </button>
                  </div>
                )}

                {selectedElement && (
                  <div className='mb-2'>
                    <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Label</label>
                    <input
                      className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                      value={selectedElement.label}
                      onChange={(event) => updateSelectedElement({ label: event.target.value })}
                    />
                  </div>
                )}

                {selectedElement && (selectedElement.type === 'navigation_next' || selectedElement.type === 'navigation_prev') && (
                  <div className='space-y-2'>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Button text</label>
                      <input
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.navLabel || ''}
                        onChange={(event) => updateSelectedElement({ navLabel: event.target.value })}
                      />
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Icon</label>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.iconUrl || ''}
                        onChange={(event) => updateSelectedElement({ iconUrl: event.target.value })}
                      >
                        <option value=''>Not selected</option>
                        {addFallbackAssetOption(
                          iconAssetOptions,
                          selectedElement.iconUrl,
                          `Current icon · ${selectedElement.iconUrl || ''}`,
                        ).map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Target page</label>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.targetPageId || ''}
                        onChange={(event) => updateSelectedElement({ targetPageId: event.target.value })}
                      >
                        <option value=''>Not selected</option>
                        {pages
                          .filter((page) => page.id !== activePageId)
                          .map((page, index) => (
                            <option key={page.id} value={page.id}>
                              {page.name || `Page ${index + 1}`}
                            </option>
                          ))}
                      </select>
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Transition video asset</label>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.transitionVideoUrl || ''}
                        onChange={(event) => updateSelectedElement({ transitionVideoUrl: event.target.value })}
                      >
                        <option value=''>Not selected</option>
                        {addFallbackAssetOption(
                          transitionVideoAssetOptions,
                          selectedElement.transitionVideoUrl,
                          `Current video · ${selectedElement.transitionVideoUrl || ''}`,
                        ).map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Back transition mode</label>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.transitionReverseMode || 'auto_reverse'}
                        onChange={(event) =>
                          updateSelectedElement({
                            transitionReverseMode: event.target.value === 'separate_video' ? 'separate_video' : 'auto_reverse',
                          })
                        }
                      >
                        <option value='auto_reverse'>Auto reverse transition video</option>
                        <option value='separate_video'>Use separate back-transition video</option>
                      </select>
                    </div>
                    {selectedElement.transitionReverseMode === 'separate_video' && (
                      <div>
                        <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Back transition video asset</label>
                        <select
                          className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                          value={selectedElement.reverseVideoUrl || ''}
                          onChange={(event) => updateSelectedElement({ reverseVideoUrl: event.target.value })}
                        >
                          <option value=''>Not selected</option>
                          {addFallbackAssetOption(
                            transitionVideoAssetOptions,
                            selectedElement.reverseVideoUrl,
                            `Current back video · ${selectedElement.reverseVideoUrl || ''}`,
                          ).map((option) => (
                            <option key={option.value} value={option.value}>
                              {option.label}
                            </option>
                          ))}
                        </select>
                      </div>
                    )}
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Transition duration (sec)</label>
                      <input
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        type='number'
                        min='0.2'
                        step='0.1'
                        value={selectedElement.transitionDurationSec ?? 0.7}
                        onChange={(event) => updateSelectedElement({ transitionDurationSec: Number(event.target.value || 0.7) })}
                      />
                    </div>
                    <div className='flex gap-2 pt-1'>
                      <BaseButton small color='lightDark' label='Preview Forward' onClick={() => openTransitionPreview('forward')} />
                      <BaseButton small color='lightDark' label='Preview Back' onClick={() => openTransitionPreview('back')} />
                    </div>
                  </div>
                )}

                {selectedElement && selectedElement.type === 'tooltip' && (
                  <div className='space-y-2'>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Icon</label>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.iconUrl || ''}
                        onChange={(event) => updateSelectedElement({ iconUrl: event.target.value })}
                      >
                        <option value=''>Not selected</option>
                        {addFallbackAssetOption(
                          iconAssetOptions,
                          selectedElement.iconUrl,
                          `Current icon · ${selectedElement.iconUrl || ''}`,
                        ).map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Tooltip title</label>
                      <input
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.tooltipTitle || ''}
                        onChange={(event) => updateSelectedElement({ tooltipTitle: event.target.value })}
                      />
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Tooltip text</label>
                      <textarea
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        rows={4}
                        value={selectedElement.tooltipText || ''}
                        onChange={(event) => updateSelectedElement({ tooltipText: event.target.value })}
                      />
                    </div>
                  </div>
                )}

                {selectedElement && selectedElement.type === 'description' && (
                  <div className='space-y-2'>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Icon</label>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.iconUrl || ''}
                        onChange={(event) => updateSelectedElement({ iconUrl: event.target.value })}
                      >
                        <option value=''>Not selected</option>
                        {addFallbackAssetOption(
                          iconAssetOptions,
                          selectedElement.iconUrl,
                          `Current icon · ${selectedElement.iconUrl || ''}`,
                        ).map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Description title</label>
                      <input
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.descriptionTitle || ''}
                        onChange={(event) => updateSelectedElement({ descriptionTitle: event.target.value })}
                      />
                    </div>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>Description text</label>
                      <textarea
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        rows={5}
                        value={selectedElement.descriptionText || ''}
                        onChange={(event) => updateSelectedElement({ descriptionText: event.target.value })}
                      />
                    </div>
                  </div>
                )}

                {selectedElement && (selectedElement.type === 'video_player' || selectedElement.type === 'audio_player') && (
                  <div className='space-y-2'>
                    <div>
                      <label className='mb-1 block text-[11px] font-semibold text-gray-600'>
                        {selectedElement.type === 'video_player' ? 'Video asset' : 'Audio asset'}
                      </label>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.mediaUrl || ''}
                        onChange={(event) => updateSelectedElement({ mediaUrl: event.target.value })}
                      >
                        <option value=''>Not selected</option>
                        {addFallbackAssetOption(
                          selectedElement.type === 'video_player' ? videoAssetOptions : audioAssetOptions,
                          selectedElement.mediaUrl,
                          `Current media · ${selectedElement.mediaUrl || ''}`,
                        ).map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <label className='flex items-center gap-2 text-[11px] text-gray-700'>
                      <input
                        type='checkbox'
                        checked={Boolean(selectedElement.mediaAutoplay)}
                        onChange={(event) => updateSelectedElement({ mediaAutoplay: event.target.checked })}
                      />
                      Autoplay
                    </label>
                    <label className='flex items-center gap-2 text-[11px] text-gray-700'>
                      <input
                        type='checkbox'
                        checked={Boolean(selectedElement.mediaLoop)}
                        onChange={(event) => updateSelectedElement({ mediaLoop: event.target.checked })}
                      />
                      Loop
                    </label>
                    {selectedElement.type === 'video_player' && (
                      <label className='flex items-center gap-2 text-[11px] text-gray-700'>
                        <input
                          type='checkbox'
                          checked={Boolean(selectedElement.mediaMuted)}
                          onChange={(event) => updateSelectedElement({ mediaMuted: event.target.checked })}
                        />
                        Muted
                      </label>
                    )}
                  </div>
                )}

                {selectedElement && selectedElement.type === 'gallery' && (
                  <div className='space-y-2'>
                    <div className='flex items-center justify-between'>
                      <p className='text-[11px] font-semibold text-gray-600'>Gallery cards</p>
                      <button type='button' className='text-xs text-blue-700 hover:underline' onClick={addGalleryCard}>
                        + Add card
                      </button>
                    </div>
                    {(selectedElement.galleryCards || []).map((card, index) => (
                      <div key={card.id} className='rounded border border-gray-200 p-2 space-y-2'>
                        <div className='flex items-center justify-between'>
                          <p className='text-[11px] font-semibold text-gray-700'>Card {index + 1}</p>
                          <button type='button' className='text-xs text-red-600 hover:underline' onClick={() => removeGalleryCard(card.id)}>
                            Remove
                          </button>
                        </div>
                        <select
                          className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                          value={card.imageUrl}
                          onChange={(event) => updateGalleryCard(card.id, { imageUrl: event.target.value })}
                        >
                          <option value=''>Image asset</option>
                          {addFallbackAssetOption(imageAssetOptions, card.imageUrl, `Current image · ${card.imageUrl}`).map((option) => (
                            <option key={option.value} value={option.value}>
                              {option.label}
                            </option>
                          ))}
                        </select>
                        <input
                          className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                          placeholder='Title'
                          value={card.title}
                          onChange={(event) => updateGalleryCard(card.id, { title: event.target.value })}
                        />
                        <textarea
                          className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                          placeholder='Description'
                          rows={3}
                          value={card.description}
                          onChange={(event) => updateGalleryCard(card.id, { description: event.target.value })}
                        />
                      </div>
                    ))}
                  </div>
                )}

                {selectedElement && selectedElement.type === 'carousel' && (
                  <div className='space-y-2'>
                    <div className='rounded border border-gray-200 p-2 space-y-2'>
                      <p className='text-[11px] font-semibold text-gray-700'>Navigation icons</p>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.carouselPrevIconUrl || ''}
                        onChange={(event) => updateSelectedElement({ carouselPrevIconUrl: event.target.value })}
                      >
                        <option value=''>Previous icon</option>
                        {addFallbackAssetOption(
                          iconAssetOptions,
                          selectedElement.carouselPrevIconUrl,
                          `Current prev icon · ${selectedElement.carouselPrevIconUrl || ''}`,
                        ).map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                      <select
                        className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                        value={selectedElement.carouselNextIconUrl || ''}
                        onChange={(event) => updateSelectedElement({ carouselNextIconUrl: event.target.value })}
                      >
                        <option value=''>Next icon</option>
                        {addFallbackAssetOption(
                          iconAssetOptions,
                          selectedElement.carouselNextIconUrl,
                          `Current next icon · ${selectedElement.carouselNextIconUrl || ''}`,
                        ).map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div className='flex items-center justify-between'>
                      <p className='text-[11px] font-semibold text-gray-600'>Carousel slides</p>
                      <button type='button' className='text-xs text-blue-700 hover:underline' onClick={addCarouselSlide}>
                        + Add slide
                      </button>
                    </div>
                    {(selectedElement.carouselSlides || []).map((slide, index) => (
                      <div key={slide.id} className='rounded border border-gray-200 p-2 space-y-2'>
                        <div className='flex items-center justify-between'>
                          <p className='text-[11px] font-semibold text-gray-700'>Slide {index + 1}</p>
                          <button
                            type='button'
                            className='text-xs text-red-600 hover:underline'
                            onClick={() => removeCarouselSlide(slide.id)}
                          >
                            Remove
                          </button>
                        </div>
                        <select
                          className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                          value={slide.imageUrl}
                          onChange={(event) => updateCarouselSlide(slide.id, { imageUrl: event.target.value })}
                        >
                          <option value=''>Image asset</option>
                          {addFallbackAssetOption(imageAssetOptions, slide.imageUrl, `Current image · ${slide.imageUrl}`).map((option) => (
                            <option key={option.value} value={option.value}>
                              {option.label}
                            </option>
                          ))}
                        </select>
                        <input
                          className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
                          placeholder='Caption'
                          value={slide.caption}
                          onChange={(event) => updateCarouselSlide(slide.id, { caption: event.target.value })}
                        />
                      </div>
                    ))}
                  </div>
                )}
              </>
            )}
          </div>
        )}

        {pages.length > 0 && (
          <div
            className='fixed z-40 w-60 border border-gray-200 rounded-lg bg-white shadow-xl'
            style={{ left: menuPosition.x, top: menuPosition.y }}
          >
            <div
              className='flex items-center justify-between px-3 py-2 border-b border-gray-200 cursor-move bg-gray-50 rounded-t-lg'
              onMouseDown={onMenuDragStart}
            >
              <span className='text-xs font-bold uppercase'>Constructor Menu</span>
              <button type='button' onClick={() => setIsMenuOpen((prev) => !prev)}>
                <BaseIcon path={mdiMenu} size={18} />
              </button>
            </div>

            {isMenuOpen && (
              <div className='p-2 space-y-1'>
                <button type='button' className='menu-action-btn' onClick={() => selectMenuItemForEdit('background_image')}>
                  <BaseIcon path={mdiImageMultiple} size={16} />
                  <span>Background Image</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => selectMenuItemForEdit('background_video')}>
                  <BaseIcon path={mdiViewCarousel} size={16} />
                  <span>Background Video</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => selectMenuItemForEdit('background_audio')}>
                  <BaseIcon path={mdiTooltipText} size={16} />
                  <span>Background Audio</span>
                </button>
                <button
                  type='button'
                  className='menu-action-btn'
                  onClick={() => {
                    const prevElement = createDefaultElement('navigation_prev', elements.length);
                    const nextElement = createDefaultElement('navigation_next', elements.length + 1);
                    setElements((prev) => [...prev, prevElement, nextElement]);
                    selectElementForEdit(nextElement.id);
                    setSuccessMessage('Navigation buttons added. Configure transition in editor.');
                    setErrorMessage('');
                  }}
                >
                  <BaseIcon path={mdiSwapHorizontal} size={16} />
                  <span>Add Navigation</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => selectMenuItemForEdit('create_transition')}>
                  <BaseIcon path={mdiSwapHorizontal} size={16} />
                  <span>Add Transition</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => addElement('gallery')}>
                  <BaseIcon path={mdiImageMultiple} size={16} />
                  <span>Add Gallery</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => addElement('carousel')}>
                  <BaseIcon path={mdiViewCarousel} size={16} />
                  <span>Add Carousel</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => addElement('tooltip')}>
                  <BaseIcon path={mdiTooltipText} size={16} />
                  <span>Add Tooltip</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => addElement('description')}>
                  <BaseIcon path={mdiText} size={16} />
                  <span>Add Description</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => addElement('video_player')}>
                  <BaseIcon path={mdiViewCarousel} size={16} />
                  <span>Add Video Player</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={() => addElement('audio_player')}>
                  <BaseIcon path={mdiTooltipText} size={16} />
                  <span>Add Audio Player</span>
                </button>
                <button type='button' className='menu-action-btn' onClick={createPage} disabled={isCreatingPage}>
                  <BaseIcon path={mdiPlus} size={16} />
                  <span>{isCreatingPage ? 'Creating Page...' : 'Create New Page'}</span>
                </button>
                <button type='button' className='menu-action-btn !text-blue-700' onClick={saveConstructor} disabled={isSaving}>
                  <BaseIcon path={mdiContentSave} size={16} />
                  <span>{isSaving ? 'Saving...' : 'Save'}</span>
                </button>
                <button
                  type='button'
                  className='menu-action-btn !text-red-700'
                  onClick={() => router.push(projectId ? `/projects/${projectId}` : '/projects/projects-list')}
                >
                  <BaseIcon path={mdiExitToApp} size={16} />
                  <span>Exit</span>
                </button>
              </div>
            )}
          </div>
        )}
      </div>

      {transitionPreview && (
        <div className='fixed inset-0 z-50 bg-black/95 flex items-center justify-center'>
          <video
            ref={transitionVideoRef}
            src={resolveAssetPlaybackUrl(
              transitionPreview.reverseMode === 'separate' ? transitionPreview.reverseVideoUrl || '' : transitionPreview.videoUrl,
            )}
            className='h-full w-full object-cover'
            muted
            playsInline
            autoPlay={transitionPreview.reverseMode !== 'reverse'}
            preload='auto'
          />
          <div className='absolute bottom-4 left-4 text-xs text-white/80'>{transitionPreview.title}</div>
          <button
            type='button'
            className='absolute top-4 right-4 rounded bg-white/20 px-3 py-1 text-xs text-white hover:bg-white/30'
            onClick={() => setTransitionPreview(null)}
          >
            Close
          </button>
        </div>
      )}

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

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

export default ConstructorPage;
