/**
 * BradlyPlayer — Drop-in replacement for Remotion's <Player>.
 *
 * Frame-based composition renderer with:
 * - requestAnimationFrame loop for smooth playback
 * - Imperative API via ref (play, pause, seekTo, getCurrentFrame)
 * - Event system (frameupdate, play, pause, ended)
 * - Optional built-in controls
 * - Loop and autoPlay support
 *
 * Usage:
 *   <BradlyPlayer
 *     ref={playerRef}
 *     component={MyComposition}
 *     inputProps={{ ... }}
 *     durationInFrames={150}
 *     compositionWidth={1080}
 *     compositionHeight={1920}
 *     fps={30}
 *     controls
 *   />
 */
import React, {
  useState,
  useEffect,
  useRef,
  useCallback,
  useImperativeHandle,
  forwardRef,
  useMemo,
} from 'react';
import { PlayerStateContext, VideoConfigContext, SequenceFrameContext } from './PlayerContext';
import type { BradlyPlayerRef, VideoConfig } from './types';
import { PlayerControls } from './PlayerControls';

interface BradlyPlayerProps<T> {
  /** The React component to render as the composition */
  component: React.ComponentType<T>;
  /** Props to pass to the composition component */
  inputProps: T;
  /** Total duration of the composition in frames */
  durationInFrames: number;
  /** Width of the composition in pixels */
  compositionWidth: number;
  /** Height of the composition in pixels */
  compositionHeight: number;
  /** Frames per second */
  fps: number;
  /** Show built-in playback controls */
  controls?: boolean;
  /** Loop playback */
  loop?: boolean;
  /** Auto-start playback */
  autoPlay?: boolean;
  /** Allow clicking the player to toggle play/pause */
  clickToPlay?: boolean;
  /** CSS styles for the outer container */
  style?: React.CSSProperties;
  /** CSS class for the outer container */
  className?: string;
}

function BradlyPlayerInner<T>(
  props: BradlyPlayerProps<T>,
  ref: React.ForwardedRef<BradlyPlayerRef>
) {
  const {
    component: Component,
    inputProps,
    durationInFrames,
    compositionWidth,
    compositionHeight,
    fps,
    controls = false,
    loop = false,
    autoPlay = false,
    clickToPlay = true,
    style,
    className,
  } = props;

  const [frame, setFrame] = useState(0);
  const [playing, setPlaying] = useState(autoPlay);
  const frameRef = useRef(0);
  const playingRef = useRef(autoPlay);
  const rafRef = useRef<number>(0);
  const lastTimeRef = useRef<number>(0);
  const listenersRef = useRef<Map<string, Set<(e: any) => void>>>(new Map());

  // Keep refs in sync with state
  useEffect(() => {
    frameRef.current = frame;
  }, [frame]);

  useEffect(() => {
    playingRef.current = playing;
  }, [playing]);

  // ── Event System ──

  const emit = useCallback((event: string, detail: any) => {
    const handlers = listenersRef.current.get(event);
    if (handlers) {
      for (const handler of handlers) {
        handler({ detail });
      }
    }
  }, []);

  // ── Frame Loop ──

  useEffect(() => {
    if (!playing) return;

    lastTimeRef.current = performance.now();

    const tick = (now: number) => {
      const elapsed = now - lastTimeRef.current;
      const frameDuration = 1000 / fps;

      if (elapsed >= frameDuration) {
        const framesToAdvance = Math.floor(elapsed / frameDuration);
        lastTimeRef.current = now - (elapsed % frameDuration);

        setFrame((prev) => {
          let next = prev + framesToAdvance;

          if (next >= durationInFrames) {
            if (loop) {
              next = next % durationInFrames;
            } else {
              next = durationInFrames - 1;
              // Stop playback
              setPlaying(false);
              emit('ended', { frame: next });
              return next;
            }
          }

          emit('frameupdate', { frame: next });
          return next;
        });
      }

      rafRef.current = requestAnimationFrame(tick);
    };

    rafRef.current = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(rafRef.current);
    };
  }, [playing, fps, durationInFrames, loop, emit]);

  // ── Imperative API ──

  const play = useCallback(() => {
    setPlaying(true);
    emit('play', { frame: frameRef.current });
  }, [emit]);

  const pause = useCallback(() => {
    setPlaying(false);
    emit('pause', { frame: frameRef.current });
  }, [emit]);

  const toggle = useCallback(() => {
    if (playingRef.current) {
      pause();
    } else {
      play();
    }
  }, [play, pause]);

  const seekTo = useCallback((targetFrame: number) => {
    const clamped = Math.max(0, Math.min(durationInFrames - 1, Math.round(targetFrame)));
    setFrame(clamped);
    frameRef.current = clamped;
    emit('frameupdate', { frame: clamped });
  }, [durationInFrames, emit]);

  const getCurrentFrame = useCallback(() => frameRef.current, []);
  const isPlaying = useCallback(() => playingRef.current, []);
  const getDuration = useCallback(() => durationInFrames, [durationInFrames]);

  const addEventListener = useCallback(
    (event: string, handler: (e: any) => void) => {
      if (!listenersRef.current.has(event)) {
        listenersRef.current.set(event, new Set());
      }
      listenersRef.current.get(event)!.add(handler);
    },
    []
  );

  const removeEventListener = useCallback(
    (event: string, handler: (e: any) => void) => {
      listenersRef.current.get(event)?.delete(handler);
    },
    []
  );

  useImperativeHandle(ref, () => ({
    play,
    pause,
    toggle,
    seekTo,
    getCurrentFrame,
    isPlaying,
    getDuration,
    addEventListener,
    removeEventListener,
  }), [play, pause, toggle, seekTo, getCurrentFrame, isPlaying, getDuration, addEventListener, removeEventListener]);

  // ── Context Values ──

  const videoConfig: VideoConfig = useMemo(() => ({
    fps,
    width: compositionWidth,
    height: compositionHeight,
    durationInFrames,
  }), [fps, compositionWidth, compositionHeight, durationInFrames]);

  const playerState = useMemo(() => ({
    frame,
    playing,
  }), [frame, playing]);

  // ── Click to play/pause ──

  const handleClick = useCallback(() => {
    if (clickToPlay) {
      toggle();
    }
  }, [clickToPlay, toggle]);

  return (
    <div
      style={{
        position: 'relative',
        overflow: 'hidden',
        backgroundColor: 'transparent',
        ...style,
      }}
      className={className}
    >
      {/* Composition viewport — maintains aspect ratio */}
      <div
        style={{
          position: 'relative',
          width: '100%',
          height: controls ? 'calc(100% - 32px)' : '100%',
          overflow: 'hidden',
        }}
        onClick={handleClick}
      >
        {/* Inner composition — scaled to fit */}
        <div
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            width: compositionWidth,
            height: compositionHeight,
            transformOrigin: 'top left',
            // Scale composition to fit the container
            // We use CSS containment for performance
          }}
          ref={(el) => {
            if (el && el.parentElement) {
              const parent = el.parentElement;
              const scaleX = parent.clientWidth / compositionWidth;
              const scaleY = parent.clientHeight / compositionHeight;
              const scale = Math.min(scaleX, scaleY);
              el.style.transform = `scale(${scale})`;
              el.style.left = `${(parent.clientWidth - compositionWidth * scale) / 2}px`;
              el.style.top = `${(parent.clientHeight - compositionHeight * scale) / 2}px`;
            }
          }}
        >
          <VideoConfigContext.Provider value={videoConfig}>
            <PlayerStateContext.Provider value={playerState}>
              <SequenceFrameContext.Provider value={null}>
                <Component {...inputProps} />
              </SequenceFrameContext.Provider>
            </PlayerStateContext.Provider>
          </VideoConfigContext.Provider>
        </div>
      </div>

      {/* Controls */}
      {controls && (
        <PlayerControls
          frame={frame}
          durationInFrames={durationInFrames}
          fps={fps}
          playing={playing}
          onPlay={play}
          onPause={pause}
          onSeek={seekTo}
        />
      )}
    </div>
  );
}

// forwardRef with generic support
export const BradlyPlayer = forwardRef(BradlyPlayerInner) as <T>(
  props: BradlyPlayerProps<T> & { ref?: React.ForwardedRef<BradlyPlayerRef>; key?: React.Key }
) => React.ReactElement | null;
