import { useEffect, useRef, useState, type CSSProperties } from "react";
import { GlitchSlideCarousel } from "./GlitchSlideCarousel";
import { PlayResumeControl } from "./PlayResumeControl";
import { SlideOverlayHost } from "./SlideOverlayHost";
import { VideoSkeletonPlayer } from "./VideoSkeletonPlayer";
import {
  computeCandidateIndex,
  getBrowseAxis,
  hasSelectionChanged
} from "../lib/gestureController";
import { emitClientEvent } from "../lib/eventLogger";
import { preloadSlideModules } from "../lib/slideRegistry";
import {
  preloadVideoSegment,
  preloadVideoSegments
} from "../lib/videoPreload";
import {
  clampGlobalTime,
  getActiveGlitchSlide,
  getActiveSegment,
  getActiveSlide,
  getCompatibleGlitchSlides,
  getNextSegment,
  getUpcomingSlides
} from "../lib/timeline";
import { createDeviceContext } from "../lib/voteApi";
import type { ClientEvent, VoteDirection, VoteRequest } from "../types/events";
import type { PresentationDefinition, SlideRegistry } from "../types/lecture";
import type {
  GlitchPlayerMode,
  PauseMode,
  PendingVote,
  ViewportMode
} from "../types/player";

export interface GlitchPlayerProps {
  presentation: PresentationDefinition;
  slideRegistry: SlideRegistry;
  mode?: GlitchPlayerMode;
  targetSlideId?: string;
  targetSlideIndex?: number;
  onEvent?: (event: ClientEvent) => void;
  onVoteCommit?: (vote: VoteRequest) => Promise<void> | void;
  initialSessionId?: string;
}

interface ScrubState {
  isScrubbing: boolean;
  previewTimeSec: number;
  resumeWasManuallyPaused: boolean;
}

interface InteractionState {
  pointerId: number | null;
  startX: number;
  startY: number;
  dragSpan: number;
  dragOffsetPx: number;
  isSettling: boolean;
  resumeWasManuallyPaused: boolean;
  baselineGlitchSlideId: string | null;
  previewGlitchSlideId: string | null;
}

const SETTLE_DURATION_MS = 220;
const CHROME_HIDE_DELAY_MS = 1600;

function getOverrideKey(slideId: string, viewportMode: ViewportMode) {
  return `${slideId}:${viewportMode}`;
}

function getViewportMode(width: number, height: number): ViewportMode {
  if (width <= 900) {
    return height >= width ? "mobile-portrait" : "mobile-landscape";
  }

  return "desktop";
}

function formatClockTime(totalSeconds: number) {
  const clamped = Math.max(0, Math.floor(totalSeconds));
  const minutes = Math.floor(clamped / 60);
  const seconds = clamped % 60;
  return `${minutes}:${seconds.toString().padStart(2, "0")}`;
}

function getTargetSlideStartTime(
  presentation: PresentationDefinition,
  targetSlideId?: string,
  targetSlideIndex?: number
) {
  const targetSlide = getTargetSlide(
    presentation,
    targetSlideId,
    targetSlideIndex
  );

  if (!targetSlide || targetSlide.startTimeSec == null) {
    return 0;
  }

  return targetSlide.startTimeSec;
}

function getTargetSlide(
  presentation: PresentationDefinition,
  targetSlideId?: string,
  targetSlideIndex?: number
) {
  if (targetSlideId) {
    return presentation.slides.find((slide) => slide.id === targetSlideId) ?? null;
  }

  if (
    typeof targetSlideIndex === "number" &&
    targetSlideIndex >= 0 &&
    targetSlideIndex < presentation.slides.length
  ) {
    return presentation.slides[targetSlideIndex];
  }

  return null;
}

export function GlitchPlayer({
  presentation,
  slideRegistry,
  mode = "playback",
  targetSlideId,
  targetSlideIndex,
  onEvent,
  onVoteCommit,
  initialSessionId
}: GlitchPlayerProps) {
  const lecture = presentation;
  const [globalTimeSec, setGlobalTimeSec] = useState(() =>
    getTargetSlideStartTime(lecture, targetSlideId, targetSlideIndex)
  );
  const [isManuallyPaused, setIsManuallyPaused] = useState(
    mode === "slide-preview"
  );
  const [sessionSlideOverrides, setSessionSlideOverrides] = useState<
    Record<string, string>
  >({});
  const [pendingVote, setPendingVote] = useState<PendingVote | null>(null);
  const [isVoteOverlayVisible, setIsVoteOverlayVisible] = useState(false);
  const [voteChoice, setVoteChoice] = useState<VoteDirection | null>(null);
  const [isSubmittingVote, setIsSubmittingVote] = useState(false);
  const [isDragging, setIsDragging] = useState(false);
  const [isSlideMediaBlocking, setIsSlideMediaBlocking] = useState(false);
  const [videoError, setVideoError] = useState<string | null>(null);
  const [viewportMode, setViewportMode] = useState<ViewportMode>(() =>
    typeof window === "undefined" || typeof document === "undefined"
      ? "desktop"
      : getViewportMode(
          document.documentElement.clientWidth,
          document.documentElement.clientHeight
        )
  );
  const [sessionId] = useState(
    () => initialSessionId ?? `sess-${Math.random().toString(36).slice(2, 10)}`
  );
  const [scrubState, setScrubState] = useState<ScrubState>({
    isScrubbing: false,
    previewTimeSec: 0,
    resumeWasManuallyPaused: false
  });
  const [interactionState, setInteractionState] = useState<InteractionState>({
    pointerId: null,
    startX: 0,
    startY: 0,
    dragSpan: 320,
    dragOffsetPx: 0,
    isSettling: false,
    resumeWasManuallyPaused: false,
    baselineGlitchSlideId: null,
    previewGlitchSlideId: null
  });
  const [isChromeVisible, setIsChromeVisible] = useState(true);

  const rootRef = useRef<HTMLDivElement | null>(null);
  const lastSegmentIdRef = useRef<string | null>(null);
  const lastSlideKeyRef = useRef<string | null>(null);
  const autoplayBlockedRef = useRef(false);
  const settleTimerRef = useRef<number | null>(null);
  const chromeHideTimerRef = useRef<number | null>(null);

  const activeSegment = getActiveSegment(lecture.segments, globalTimeSec);
  const nextSegment = activeSegment
    ? getNextSegment(lecture.segments, activeSegment.id)
    : null;
  const previewTargetSlide =
    mode === "slide-preview"
      ? getTargetSlide(lecture, targetSlideId, targetSlideIndex)
      : null;
  const activeSlide =
    previewTargetSlide ??
    getActiveSlide(lecture.slides, globalTimeSec, lecture.durationSec);
  const compatibleGlitchSlides = activeSlide
    ? getCompatibleGlitchSlides(activeSlide, viewportMode)
    : [];
  const overrideGlitchSlideId = activeSlide
    ? sessionSlideOverrides[getOverrideKey(activeSlide.id, viewportMode)]
    : undefined;
  const currentResolvedGlitchSlide = activeSlide
    ? getActiveGlitchSlide(activeSlide, viewportMode, overrideGlitchSlideId)
    : null;
  const previewGlitchSlide = activeSlide
    ? compatibleGlitchSlides.find(
        (candidate) => candidate.id === interactionState.previewGlitchSlideId
      ) ?? null
    : null;
  const activeGlitchSlide = previewGlitchSlide ?? currentResolvedGlitchSlide;
  const pauseMode: PauseMode = isManuallyPaused
    ? "full-paused"
    : scrubState.isScrubbing ||
        isDragging ||
        interactionState.isSettling ||
        isSlideMediaBlocking
      ? "soft-paused"
      : "playing";
  const displayedTimeSec = scrubState.isScrubbing
    ? scrubState.previewTimeSec
    : globalTimeSec;
  const highlightedSlide = getActiveSlide(
    lecture.slides,
    displayedTimeSec,
    lecture.durationSec
  );
  const isInspecting =
    isDragging || interactionState.isSettling;
  const isChromePinned =
    isManuallyPaused ||
    scrubState.isScrubbing ||
    isDragging ||
    interactionState.isSettling;
  const shouldShowScrubber = isChromePinned || isChromeVisible;
  const isFullscreenSlide = activeSlide?.displayMode === "fullscreen";
  const squareSlideHeader =
    activeSlide?.displayMode === "square" &&
    activeGlitchSlide &&
    (activeGlitchSlide.componentKey === "SquareYellow" ||
      activeGlitchSlide.componentKey === "SquareVideo") &&
    typeof (activeGlitchSlide.props as { header?: unknown }).header === "string"
      ? (activeGlitchSlide.props as { header: string }).header
      : null;
  const userOverrodeCurrentSlide = Boolean(
    activeSlide && sessionSlideOverrides[getOverrideKey(activeSlide.id, viewportMode)]
  );

  useEffect(() => {
    const rootElement = rootRef.current;

    if (!rootElement || typeof ResizeObserver === "undefined") {
      return;
    }

    const observer = new ResizeObserver((entries) => {
      const entry = entries[0];
      if (!entry) {
        return;
      }

      setViewportMode(
        getViewportMode(entry.contentRect.width, entry.contentRect.height)
      );
    });

    observer.observe(rootElement);

    return () => {
      observer.disconnect();
    };
  }, []);

  useEffect(() => {
    const targetTimeSec = getTargetSlideStartTime(
      lecture,
      targetSlideId,
      targetSlideIndex
    );
    setGlobalTimeSec(targetTimeSec);
    setIsManuallyPaused(mode === "slide-preview");
    setIsVoteOverlayVisible(false);
    setVoteChoice(null);
    setPendingVote(null);
    setIsSlideMediaBlocking(false);
  }, [lecture.id, lecture.slides, mode, targetSlideId, targetSlideIndex]);

  useEffect(() => {
    setIsSlideMediaBlocking(false);
  }, [activeSlide?.id, activeGlitchSlide?.id]);

  useEffect(() => {
    emitClientEvent(onEvent, {
      type: "lecture_loaded",
      lectureId: lecture.id
    });
  }, [lecture.id, onEvent]);

  useEffect(() => {
    if (!activeSegment) {
      return;
    }

    preloadVideoSegment(activeSegment);

    if (nextSegment) {
      preloadVideoSegment(nextSegment);
    }
  }, [activeSegment, nextSegment]);

  useEffect(() => {
    preloadVideoSegments(lecture.segments.slice(0, 2));
  }, [lecture.segments]);

  useEffect(() => {
    if (!activeSlide) {
      return;
    }

    void preloadSlideModules(slideRegistry, [activeSlide]);

    const upcomingSlides = getUpcomingSlides(
      lecture.slides,
      globalTimeSec,
      lecture.durationSec,
      2
    );
    void preloadSlideModules(slideRegistry, upcomingSlides);
  }, [activeSlide, globalTimeSec, lecture.durationSec, lecture.slides, slideRegistry]);

  useEffect(() => {
    if (!activeSegment) {
      lastSegmentIdRef.current = null;
      return;
    }

    if (lastSegmentIdRef.current !== activeSegment.id) {
      lastSegmentIdRef.current = activeSegment.id;
      emitClientEvent(onEvent, {
        type: "segment_changed",
        lectureId: lecture.id,
        segmentId: activeSegment.id,
        atSec: Number(globalTimeSec.toFixed(2))
      });
    }
  }, [activeSegment, globalTimeSec, lecture.id, onEvent]);

  useEffect(() => {
    const slideKey =
      activeSlide && activeGlitchSlide
        ? `${activeSlide.id}:${activeGlitchSlide.id}`
        : null;

    if (!slideKey) {
      lastSlideKeyRef.current = null;
      return;
    }

    if (lastSlideKeyRef.current !== slideKey) {
      if (activeSlide && activeGlitchSlide) {
        emitClientEvent(onEvent, {
          type: "slide_activated",
          lectureId: lecture.id,
          slideId: activeSlide.id,
          glitchSlideId: activeGlitchSlide.id,
          atSec: Number(globalTimeSec.toFixed(2))
        });
      }

      lastSlideKeyRef.current = slideKey;
    }
  }, [activeGlitchSlide, activeSlide, globalTimeSec, lecture.id, onEvent]);

  useEffect(() => {
    setInteractionState((currentState) => ({
      ...currentState,
      dragOffsetPx: 0,
      isSettling: false,
      baselineGlitchSlideId: currentResolvedGlitchSlide?.id ?? null,
      previewGlitchSlideId: null
    }));
    setIsDragging(false);
  }, [activeSlide?.id]);

  useEffect(() => {
    if (
      interactionState.pointerId !== null ||
      interactionState.isSettling ||
      isDragging
    ) {
      return;
    }

    setInteractionState((currentState) => ({
      ...currentState,
      baselineGlitchSlideId: currentResolvedGlitchSlide?.id ?? null,
      previewGlitchSlideId: null
    }));
  }, [
    currentResolvedGlitchSlide?.id,
    interactionState.pointerId,
    interactionState.isSettling,
    isDragging
  ]);

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key !== " " && event.key.toLowerCase() !== "k") {
        return;
      }

      const activeElement = document.activeElement as HTMLElement | null;
      if (
        activeElement &&
        ["INPUT", "TEXTAREA", "BUTTON"].includes(activeElement.tagName)
      ) {
        return;
      }

      event.preventDefault();

      if (pauseMode === "playing") {
        setIsManuallyPaused(true);
      } else {
        void handleResume();
      }
    };

    window.addEventListener("keydown", handleKeyDown);

    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [pauseMode, pendingVote, activeSlide, activeGlitchSlide, globalTimeSec]);

  useEffect(() => {
    return () => {
      if (settleTimerRef.current !== null) {
        window.clearTimeout(settleTimerRef.current);
      }
      if (chromeHideTimerRef.current !== null) {
        window.clearTimeout(chromeHideTimerRef.current);
      }
    };
  }, []);

  function showChrome() {
    setIsChromeVisible(true);

    if (chromeHideTimerRef.current !== null) {
      window.clearTimeout(chromeHideTimerRef.current);
      chromeHideTimerRef.current = null;
    }

    if (
      isManuallyPaused ||
      scrubState.isScrubbing ||
      isDragging ||
      interactionState.isSettling
    ) {
      return;
    }

    chromeHideTimerRef.current = window.setTimeout(() => {
      setIsChromeVisible(false);
      chromeHideTimerRef.current = null;
    }, CHROME_HIDE_DELAY_MS);
  }

  useEffect(() => {
    showChrome();
  }, [isManuallyPaused, scrubState.isScrubbing, isDragging, interactionState.isSettling]);

  function updateTime(nextTime: number) {
    setGlobalTimeSec(clampGlobalTime(nextTime, lecture.durationSec));
  }

  function handleScrubStart() {
    showChrome();
    setIsVoteOverlayVisible(false);
    setIsManuallyPaused(true);
    setScrubState({
      isScrubbing: true,
      previewTimeSec: globalTimeSec,
      resumeWasManuallyPaused: isManuallyPaused
    });
  }

  function handleScrubChange(nextValue: string) {
    const nextTime = clampGlobalTime(Number(nextValue), lecture.durationSec);
    setScrubState((currentState) => ({
      ...currentState,
      previewTimeSec: nextTime
    }));
    updateTime(nextTime);
  }

  function handleScrubEnd() {
    setScrubState((currentState) => ({
      ...currentState,
      isScrubbing: false
    }));
  }

  function handleMarkerJump(targetTimeSec: number) {
    showChrome();
    setIsVoteOverlayVisible(false);
    setIsManuallyPaused(true);
    if (settleTimerRef.current !== null) {
      window.clearTimeout(settleTimerRef.current);
      settleTimerRef.current = null;
    }
    setIsDragging(false);
    setScrubState((currentState) => ({
      ...currentState,
      isScrubbing: false,
      previewTimeSec: targetTimeSec
    }));
    setInteractionState((currentState) => ({
      ...currentState,
      pointerId: null,
      dragOffsetPx: 0,
      isSettling: false,
      previewGlitchSlideId: null
    }));
    updateTime(targetTimeSec);
  }

  function resetInteractionState() {
    if (settleTimerRef.current !== null) {
      window.clearTimeout(settleTimerRef.current);
      settleTimerRef.current = null;
    }
    setInteractionState((currentState) => ({
      ...currentState,
      pointerId: null,
      dragOffsetPx: 0,
      isSettling: false,
      previewGlitchSlideId: null
    }));
    setIsDragging(false);
  }

  function settleTrack(targetOffsetPx: number, onComplete: () => void) {
    if (settleTimerRef.current !== null) {
      window.clearTimeout(settleTimerRef.current);
    }

    setIsDragging(false);
    setInteractionState((currentState) => ({
      ...currentState,
      pointerId: null,
      dragOffsetPx: targetOffsetPx,
      isSettling: true
    }));

    settleTimerRef.current = window.setTimeout(() => {
      settleTimerRef.current = null;
      onComplete();
    }, SETTLE_DURATION_MS);
  }

  function handlePointerDown(event: React.PointerEvent<HTMLDivElement>) {
    if (!activeSlide || !currentResolvedGlitchSlide) {
      return;
    }

    showChrome();
    setIsVoteOverlayVisible(false);
    const bounds = event.currentTarget.getBoundingClientRect();
    if (typeof event.currentTarget.setPointerCapture === "function") {
      event.currentTarget.setPointerCapture(event.pointerId);
    }
    const browseAxis = getBrowseAxis(viewportMode);

    setInteractionState({
      pointerId: event.pointerId,
      startX: event.clientX,
      startY: event.clientY,
      dragSpan:
        browseAxis === "horizontal"
          ? Math.max(bounds.width, 240)
          : Math.max(bounds.height, 240),
      dragOffsetPx: 0,
      isSettling: false,
      resumeWasManuallyPaused: isManuallyPaused,
      baselineGlitchSlideId: currentResolvedGlitchSlide.id,
      previewGlitchSlideId: null
    });
  }

  function handlePointerMove(event: React.PointerEvent<HTMLDivElement>) {
    if (!activeSlide || interactionState.pointerId !== event.pointerId) {
      return;
    }

    const browseAxis = getBrowseAxis(viewportMode);
    const deltaX = event.clientX - interactionState.startX;
    const deltaY = event.clientY - interactionState.startY;
    const primaryDelta = browseAxis === "horizontal" ? deltaX : deltaY;

    if (!isDragging && Math.abs(primaryDelta) < 24) {
      return;
    }
    showChrome();

    if (!isDragging) {
      setIsDragging(true);
      if (!interactionState.resumeWasManuallyPaused) {
        emitClientEvent(onEvent, {
          type: "soft_pause_started",
          lectureId: lecture.id,
          slideId: activeSlide.id,
          atSec: Number(globalTimeSec.toFixed(2))
        });
      }
    }

    const baselineIndex = compatibleGlitchSlides.findIndex(
      (candidate) => candidate.id === interactionState.baselineGlitchSlideId
    );
    const clampedPrimaryDelta = Math.min(
      Math.max(
        primaryDelta,
        -((compatibleGlitchSlides.length - 1) - Math.max(baselineIndex, 0)) *
          interactionState.dragSpan
      ),
      Math.max(baselineIndex, 0) * interactionState.dragSpan
    );
    const nextIndex = computeCandidateIndex({
      baselineIndex: baselineIndex === -1 ? 0 : baselineIndex,
      dragDistance: -clampedPrimaryDelta,
      totalCandidates: compatibleGlitchSlides.length,
      dragSpan: interactionState.dragSpan
    });
    const nextCandidate = compatibleGlitchSlides[nextIndex];

    if (
      nextCandidate &&
      nextCandidate.id !== interactionState.previewGlitchSlideId &&
      nextCandidate.id !== interactionState.baselineGlitchSlideId
    ) {
      emitClientEvent(onEvent, {
        type: "glitch_slide_changed",
        lectureId: lecture.id,
        slideId: activeSlide.id,
        from:
          interactionState.previewGlitchSlideId ??
          interactionState.baselineGlitchSlideId ??
          nextCandidate.id,
        to: nextCandidate.id,
        atSec: Number(globalTimeSec.toFixed(2))
      });
    }

    setInteractionState((currentState) => ({
      ...currentState,
      dragOffsetPx: clampedPrimaryDelta,
      previewGlitchSlideId: nextCandidate?.id ?? null
    }));
  }

  function handlePointerEnd(pointerId: number) {
    if (!activeSlide || interactionState.pointerId !== pointerId) {
      resetInteractionState();
      return;
    }

    const finalGlitchSlideId =
      interactionState.previewGlitchSlideId ??
      interactionState.baselineGlitchSlideId ??
      currentResolvedGlitchSlide?.id ??
      null;
    const baselineGlitchSlideId =
      interactionState.baselineGlitchSlideId ?? currentResolvedGlitchSlide?.id ?? null;
    const selectionChanged = hasSelectionChanged(
      baselineGlitchSlideId,
      finalGlitchSlideId
    );

    if (!isDragging) {
      if (activeSlide.displayMode === "fullscreen") {
        resetInteractionState();
        handleTogglePlayback();
        return;
      }

      resetInteractionState();
      return;
    }

    if (!selectionChanged) {
      const resumeWasManuallyPaused = interactionState.resumeWasManuallyPaused;
      settleTrack(0, () => {
        if (!resumeWasManuallyPaused) {
          emitClientEvent(onEvent, {
            type: "soft_pause_ended",
            lectureId: lecture.id,
            slideId: activeSlide.id,
            atSec: Number(globalTimeSec.toFixed(2))
          });
        }

        if (!resumeWasManuallyPaused) {
          setPendingVote(null);
        }
        resetInteractionState();
      });
      return;
    }

    if (baselineGlitchSlideId && finalGlitchSlideId) {
      setSessionSlideOverrides((currentOverrides) => ({
        ...currentOverrides,
        [getOverrideKey(activeSlide.id, viewportMode)]: finalGlitchSlideId
      }));

      const baselineIndex = compatibleGlitchSlides.findIndex(
        (candidate) => candidate.id === baselineGlitchSlideId
      );
      const finalIndex = compatibleGlitchSlides.findIndex(
        (candidate) => candidate.id === finalGlitchSlideId
      );
      const settleOffsetPx =
        baselineIndex === -1 || finalIndex === -1
          ? 0
          : (baselineIndex - finalIndex) * interactionState.dragSpan;
      const resumeWasManuallyPaused = interactionState.resumeWasManuallyPaused;

      settleTrack(settleOffsetPx, () => {
        if (!resumeWasManuallyPaused) {
          emitClientEvent(onEvent, {
            type: "soft_pause_ended",
            lectureId: lecture.id,
            slideId: activeSlide.id,
            atSec: Number(globalTimeSec.toFixed(2))
          });
        }

        if (!resumeWasManuallyPaused) {
          setPendingVote(null);
        }
        resetInteractionState();
      });
    }
  }

  async function handleResume() {
    setIsVoteOverlayVisible(false);
    if (pendingVote) {
      const vote: VoteRequest = {
        lectureId: pendingVote.lectureId,
        slideId: pendingVote.slideId,
        previousGlitchSlideId: pendingVote.previousGlitchSlideId,
        selectedGlitchSlideId: pendingVote.selectedGlitchSlideId,
        sessionId,
        timestamp: new Date().toISOString(),
        deviceContext: createDeviceContext(viewportMode)
      };

      await onVoteCommit?.(vote);
      emitClientEvent(onEvent, {
        type: "vote_committed",
        lectureId: vote.lectureId,
        slideId: vote.slideId,
        selectedGlitchSlideId: vote.selectedGlitchSlideId,
        previousGlitchSlideId: vote.previousGlitchSlideId,
        atSec: Number(globalTimeSec.toFixed(2))
      });
      setPendingVote(null);
    }

    setIsManuallyPaused(false);
  }

  async function handleSlideVote(voteDirection: VoteDirection) {
    if (!activeSlide || !activeGlitchSlide || isSubmittingVote) {
      return;
    }

    setIsSubmittingVote(true);
    setVoteChoice(voteDirection);

    const vote: VoteRequest = {
      lectureId: lecture.id,
      slideId: activeSlide.id,
      previousGlitchSlideId: activeGlitchSlide.id,
      selectedGlitchSlideId: activeGlitchSlide.id,
      voteDirection,
      sessionId,
      timestamp: new Date().toISOString(),
      deviceContext: createDeviceContext(viewportMode)
    };

    try {
      await onVoteCommit?.(vote);
      emitClientEvent(onEvent, {
        type: "vote_committed",
        lectureId: vote.lectureId,
        slideId: vote.slideId,
        selectedGlitchSlideId: vote.selectedGlitchSlideId,
        previousGlitchSlideId: vote.previousGlitchSlideId,
        voteDirection,
        atSec: Number(globalTimeSec.toFixed(2))
      });
    } finally {
      setIsSubmittingVote(false);
    }
  }

  function handleTogglePlayback() {
    showChrome();

    if (pauseMode === "playing") {
      setIsManuallyPaused(true);
      setVoteChoice(null);
      setIsVoteOverlayVisible(true);
      return;
    }

    if (isManuallyPaused) {
      void handleResume();
    }
  }

  function renderVoteOverlay(displayMode: "square" | "fullscreen") {
    if (
      !isVoteOverlayVisible ||
      !activeSlide ||
      !activeGlitchSlide ||
      activeSlide.displayMode !== displayMode
    ) {
      return null;
    }

    return (
      <div
        className={`lecture-player__vote-overlay lecture-player__vote-overlay--${displayMode}`}
        role="dialog"
        aria-label="Vote on slide"
        onClick={(event) => event.stopPropagation()}
      >
        <div className="lecture-player__vote-panel">
          <p className="lecture-player__vote-label">Rate This Slide</p>
          <div className="lecture-player__vote-actions">
            <button
              type="button"
              className={`lecture-player__vote-button ${
                voteChoice === "up" ? "lecture-player__vote-button--active" : ""
              }`}
              onClick={(event) => {
                event.stopPropagation();
                void handleSlideVote("up");
              }}
              disabled={isSubmittingVote}
            >
              Upvote
            </button>
            <button
              type="button"
              className={`lecture-player__vote-button ${
                voteChoice === "down" ? "lecture-player__vote-button--active" : ""
              }`}
              onClick={(event) => {
                event.stopPropagation();
                void handleSlideVote("down");
              }}
              disabled={isSubmittingVote}
            >
              Downvote
            </button>
          </div>
        </div>
      </div>
    );
  }

  function renderSlideLayer(displayMode: "square" | "fullscreen") {
    if (!activeSlide || !activeGlitchSlide) {
      if (displayMode === "fullscreen") {
        return null;
      }

      return (
        <div className="lecture-player__empty">
          <p className="eyebrow">No slide available</p>
          <p>Enroll at glitch.university, and help make this slide!.</p>
        </div>
      );
    }

    if (activeSlide.displayMode !== displayMode) {
      if (displayMode === "fullscreen") {
        return null;
      }

      return (
        <div
          className="lecture-player__empty lecture-player__empty--muted"
          aria-hidden="true"
        />
      );
    }

    return (
      <div
        className="lecture-player__gesture-layer"
        onPointerDown={handlePointerDown}
        onPointerMove={handlePointerMove}
        onPointerUp={(event) => handlePointerEnd(event.pointerId)}
        onPointerCancel={(event) => handlePointerEnd(event.pointerId)}
        onPointerLeave={(event) => {
          if (isDragging) {
            handlePointerEnd(event.pointerId);
          }
        }}
      >
        <SlideOverlayHost
          lectureId={lecture.id}
          slide={activeSlide}
          glitchSlide={activeGlitchSlide}
          candidates={compatibleGlitchSlides}
          slideRegistry={slideRegistry}
          paused={pauseMode !== "playing"}
          isInspecting={isInspecting}
          isSettling={interactionState.isSettling}
          viewportMode={viewportMode}
          dragBaselineGlitchSlideId={interactionState.baselineGlitchSlideId}
          dragOffsetPx={interactionState.dragOffsetPx}
          onMediaLoadingChange={setIsSlideMediaBlocking}
        />

        <GlitchSlideCarousel
          candidates={compatibleGlitchSlides}
          displayMode={activeSlide.displayMode}
          activeGlitchSlide={activeGlitchSlide}
          isInspecting={isInspecting}
          isDragging={isDragging}
          viewportMode={viewportMode}
        />
      </div>
    );
  }

  return (
    <div
      ref={rootRef}
      className={`lecture-player lecture-player--${viewportMode} ${
        activeSlide?.displayMode === "fullscreen" ? "lecture-player--fullscreen" : ""
      }`}
      data-testid="lecture-player"
    >
      <div className="lecture-player__stage">
        <div
          className="lecture-player__stage-frame"
          onPointerMove={() => {
            if (!isDragging && !scrubState.isScrubbing) {
              showChrome();
            }
          }}
          onPointerDown={() => showChrome()}
        >
          <VideoSkeletonPlayer
            segment={activeSegment}
            nextSegment={nextSegment}
            globalTimeSec={globalTimeSec}
            lectureDurationSec={lecture.durationSec}
            pauseMode={pauseMode}
            viewportMode={viewportMode}
            onTogglePlayback={handleTogglePlayback}
            onAutoplayBlocked={() => {
              if (autoplayBlockedRef.current) {
                return;
              }

              autoplayBlockedRef.current = true;
              setIsManuallyPaused(true);
            }}
            onSegmentError={(message) => setVideoError(message)}
            onGlobalTimeChange={updateTime}
          />

          {squareSlideHeader ? (
            <div className="lecture-player__square-header" aria-live="polite">
              {squareSlideHeader}
            </div>
          ) : null}

          <div className="lecture-player__slide-shell">
            {renderSlideLayer("square")}
            {renderVoteOverlay("square")}
          </div>
          {isFullscreenSlide ? (
            <div className="lecture-player__fullscreen-layer">
              {renderSlideLayer("fullscreen")}
              {renderVoteOverlay("fullscreen")}
            </div>
          ) : null}
          {pauseMode !== "playing" ? (
            <div className="lecture-player__overlay-control">
              <PlayResumeControl
                pauseMode={pauseMode}
                pendingVote={pendingVote}
                onResume={() => {
                  void handleResume();
                }}
                onPause={() => setIsManuallyPaused(true)}
              />
            </div>
          ) : null}
          {shouldShowScrubber ? (
            <div
              className="lecture-player__scrubber"
              style={
                {
                  "--scrub-progress": `${
                    lecture.durationSec > 0
                      ? (displayedTimeSec / lecture.durationSec) * 100
                      : 0
                  }%`
                } as CSSProperties
              }
            >

              <div className="lecture-player__scrubber-markers" aria-label="Slide markers">
                {lecture.slides.map((slide, index) => {
                  const startTimeSec = slide.startTimeSec;

                  if (startTimeSec == null) {
                    return null;
                  }

                  const markerProgress =
                    lecture.durationSec > 0
                      ? (startTimeSec / lecture.durationSec) * 100
                      : 0;
                  const isActiveMarker = highlightedSlide?.id === slide.id;

                  return (
                    <button
                      key={slide.id}
                      type="button"
                      className={`lecture-player__scrubber-marker ${
                        isActiveMarker
                          ? "lecture-player__scrubber-marker--active"
                          : ""
                      }`}
                      style={
                        {
                          "--marker-progress": `${markerProgress}%`
                        } as CSSProperties
                      }
                      aria-label={`S${index + 1}`}
                      onPointerDown={(event) => {
                        event.preventDefault();
                        event.stopPropagation();
                        handleMarkerJump(startTimeSec);
                      }}
                      onPointerUp={(event) => {
                        event.preventDefault();
                        event.stopPropagation();
                      }}
                      onClick={(event) => {
                        event.preventDefault();
                        event.stopPropagation();
                        handleMarkerJump(startTimeSec);
                      }}
                    >
                      <span className="lecture-player__scrubber-marker-label">
                        S{index + 1}
                      </span>
                    </button>
                  );
                })}
              </div>
              <input
                type="range"
                min={0}
                max={lecture.durationSec}
                step={0.01}
                value={displayedTimeSec}
                className="lecture-player__scrubber-input"
                aria-label="Lecture timeline"
                onPointerDown={(event) => {
                  event.stopPropagation();
                  handleScrubStart();
                }}
                onChange={(event) => handleScrubChange(event.currentTarget.value)}
                onPointerUp={() => handleScrubEnd()}
                onPointerCancel={() => handleScrubEnd()}
                onBlur={() => {
                  if (scrubState.isScrubbing) {
                    handleScrubEnd();
                  }
                }}
                onKeyDown={() => {
                  if (!scrubState.isScrubbing) {
                    handleScrubStart();
                  }
                }}
                onKeyUp={() => handleScrubEnd()}
              />
             
            </div>
          ) : null}
          {videoError ? (
            <div className="lecture-player__alert lecture-player__alert--overlay" role="alert">
              <strong>Segment error:</strong> {videoError}
            </div>
          ) : null}
        </div>
      </div>
    </div>
  );
}

export type PresentationPlayerProps = GlitchPlayerProps;
export const PresentationPlayer = GlitchPlayer;
export const LecturePlayer = GlitchPlayer;
