/**
 * RuntimeElement Component
 *
 * Renders a single UI element with interactive effects at runtime.
 * Handles hover, focus, active states and positioning.
 * Delegates element styling and content to UiElementRenderer.
 */

import React from 'react';
import UiElementRenderer from './UiElements/UiElementRenderer';
import { useElementEffects } from '../hooks/useElementEffects';
import { useAudioEffects } from '../hooks/useAudioEffects';
import {
  buildTransitionStyle,
  buildAppearAnimationStyle,
  hasAnyEffects,
  extractEffectProperties,
} from '../lib/elementEffects';
import type { CanvasElement } from '../types/constructor';
import type { ResolvedTransitionSettings } from '../types/transition';
import type { PreloadCacheProvider } from '../hooks/video';
import { isInfoPanelElementType } from '../lib/elementDefaults';

interface RuntimeElementProps {
  element: CanvasElement;
  onClick: () => void;
  /** Whether runtime interaction should be ignored for this element */
  isDisabled?: boolean;
  /** Optional URL resolver for preloaded blob URLs */
  resolveUrl?: (url: string | undefined) => string;
  /** Gallery card click handler */
  onGalleryCardClick?: (cardIndex: number) => void;
  /** Letterbox styles for constraining fullscreen elements to canvas bounds */
  letterboxStyles?: React.CSSProperties;
  /** Page transition settings (for slide transition cascade in carousel/gallery) */
  pageTransitionSettings?: ResolvedTransitionSettings;
  /** Preload cache provider for video elements */
  preloadCache?: PreloadCacheProvider;
  /** Whether this element's info panel is currently open (for visibility persistence) */
  isInfoPanelOpen?: boolean;
}

// Clamp position to canvas bounds (0-100%)
const clamp = (value: number, min: number, max: number) =>
  Math.min(Math.max(value, min), max);

const RuntimeElement: React.FC<RuntimeElementProps> = ({
  element,
  onClick,
  isDisabled = false,
  resolveUrl,
  onGalleryCardClick,
  letterboxStyles,
  pageTransitionSettings,
  preloadCache,
  isInfoPanelOpen = false,
}) => {
  // Clamp coordinates to canvas bounds
  const xPercent = clamp(element.xPercent ?? 50, 0, 100);
  const yPercent = clamp(element.yPercent ?? 50, 0, 100);
  const rotation = element.rotation ?? 0;

  // Extract effect properties from element using helper
  const effectProperties = extractEffectProperties(
    element as unknown as Record<string, unknown>,
  );

  // Use effects hook for interactive states
  // Pass forceVisible when info panel is open to keep trigger visible
  const {
    effectStyle,
    eventHandlers,
    onPersistClick,
    state: effectState,
  } = useElementEffects(isDisabled ? {} : effectProperties, {
    resetKey: element.id, // Reset reveal on element change
    forceVisible: isInfoPanelOpen,
  });

  // Audio effects - uses exposed state from useElementEffects
  // resolveUrl prop resolves to preloaded blob URLs via RuntimePresentation
  useAudioEffects({
    hoverAudioUrl: isDisabled ? undefined : effectProperties.hoverAudioUrl,
    clickAudioUrl: isDisabled ? undefined : effectProperties.clickAudioUrl,
    volume: parseFloat(effectProperties.audioVolume || '1'),
    isHovered: effectState.isHovered,
    isActive: effectState.isActive,
    resolveUrl, // Resolves to cached blob URLs (passed from RuntimePresentation)
    resetKey: element.id,
  });

  // Combined click handler
  // Skip toggle for info panel elements (their visibility is tied to panel open state)
  const handleClick = () => {
    if (isDisabled) {
      return;
    }
    if (!isInfoPanelElementType(element.type)) {
      onPersistClick(); // Toggle persistence state
    }
    onClick(); // Original navigation action
  };

  const handleKeyDown = (event: React.KeyboardEvent) => {
    if (event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      handleClick();
    }
  };

  // Build base position style (outer div - handles positioning + animation)
  let positionStyle: React.CSSProperties = {
    left: `${xPercent}%`,
    top: `${yPercent}%`,
    transform: `translate(-50%, -50%)${rotation ? ` rotate(${rotation}deg)` : ''}`,
    pointerEvents: 'auto',
  };

  // Add appear animation to outer div
  // Animation stays on outer div to keep positioning hack working
  if (effectProperties.appearAnimation) {
    positionStyle = {
      ...positionStyle,
      ...buildAppearAnimationStyle(effectProperties),
    };
  }

  // Build inner wrapper style for hover/focus/active effects
  // Using separate div so animation on outer div doesn't block these effects
  let innerEffectStyle: React.CSSProperties = {};
  if (hasAnyEffects(effectProperties)) {
    innerEffectStyle = {
      ...effectStyle,
      ...buildTransitionStyle(effectProperties),
    };
  }

  // Render content
  const content = (
    <UiElementRenderer
      element={element}
      resolveUrl={resolveUrl}
      onGalleryCardClick={onGalleryCardClick}
      letterboxStyles={letterboxStyles}
      pageTransitionSettings={pageTransitionSettings}
      preloadCache={preloadCache}
    />
  );

  // Check if we need the inner wrapper for effects
  const needsEffectWrapper = !isDisabled && hasAnyEffects(effectProperties);

  return (
    <div
      className='absolute cursor-pointer'
      style={positionStyle}
      onClick={handleClick}
      onKeyDown={handleKeyDown}
      tabIndex={isDisabled ? -1 : 0}
      aria-disabled={isDisabled}
      {...(!isDisabled && !needsEffectWrapper ? eventHandlers : {})}
    >
      {needsEffectWrapper ? (
        // Inner wrapper handles hover/focus/active effects independently from animation
        <div style={innerEffectStyle} {...eventHandlers}>
          {content}
        </div>
      ) : (
        content
      )}
    </div>
  );
};

export default RuntimeElement;
