"use client";

import { useState, useEffect, useCallback, useRef, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import {
  VideoPlayer,
  ChannelInfo,
  ChannelControls,
  ScheduleOverlay,
  UpNextBanner,
  NoSignal,
  ChannelPasswordModal,
} from "./components";
import type { SubtitleTrack } from "./components/video-player";
import type { LogoPosition } from "@/lib/types";
import { Cast, Maximize2, Minimize2, Volume1, Volume2, VolumeX } from "lucide-react";
import { useAuthContext } from "@/context/auth-context";
import { useChannels, useCurrentBroadcast, useEpg } from "@/hooks/use-channels";
import { useCast } from "@/hooks/use-cast";
import {
  useStreamUrl,
  fmtTime,
  calcProgress,
  calcOffsetSecs,
  minutesUntil,
  toScheduleSlots,
  findNextSlot,
} from "@/hooks/use-tv";

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const IDLE_TIMEOUT_MS = 3500;
const BANNER_THRESHOLD = 80; // show "up next" when progress ≥ this %

function logoPositionClass(pos?: LogoPosition) {
  switch (pos) {
    case "top_left":    return "top-0 left-0";
    case "bottom_left": return "bottom-0 left-0";
    case "bottom_right":return "bottom-0 right-0";
    default:            return "top-0 right-0";
  }
}

// ---------------------------------------------------------------------------
// Page
// ---------------------------------------------------------------------------

export default function TvPage() {
  return (
    <Suspense>
      <TvPageContent />
    </Suspense>
  );
}

function TvPageContent() {
  const { token } = useAuthContext();
  const router = useRouter();
  const searchParams = useSearchParams();

  // Channel list
  const { data: channels, isLoading: isLoadingChannels } = useChannels();

  // URL is the single source of truth for the active channel.
  // channelIdx is derived — never stored in state.
  const channelId = searchParams.get("channel");
  const channelIdx = channels && channelId
    ? Math.max(0, channels.findIndex((c) => c.id === channelId))
    : 0;
  const channel = channels?.[channelIdx];

  // Write a channel switch back to the URL so keyboard, buttons, and
  // guide links all stay in sync and the page is bookmarkable/refreshable.
  const switchChannel = useCallback((idx: number, list = channels) => {
    const target = list?.[idx];
    if (!target) return;
    router.replace(`/tv?channel=${target.id}`, { scroll: false });
  }, [channels, router]);

  // Overlay / idle state
  const [showOverlays, setShowOverlays] = useState(true);
  const [showSchedule, setShowSchedule] = useState(false);
  const idleTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Video ref — used to resume playback if autoplay was blocked on load
  const videoRef = useRef<HTMLVideoElement>(null);

  // Access control — persisted per channel in localStorage
  const [channelPasswords, setChannelPasswords] = useState<Record<string, string>>(() => {
    try { return JSON.parse(localStorage.getItem("channel_passwords") ?? "{}"); } catch { return {}; }
  });
  const [blockPasswords, setBlockPasswords] = useState<Record<string, string>>(() => {
    try { return JSON.parse(localStorage.getItem("block_passwords") ?? "{}"); } catch { return {}; }
  });
  const [showChannelPasswordModal, setShowChannelPasswordModal] = useState(false);
  const [showBlockPasswordModal, setShowBlockPasswordModal] = useState(false);

  const channelPassword = channel ? channelPasswords[channel.id] : undefined;

  // Stream error recovery
  const [streamError, setStreamError] = useState(false);

  // When the video ends, show no-signal until the next broadcast is detected.
  const [videoEnded, setVideoEnded] = useState(false);

  // Autoplay blocked by browser — cleared on first interaction via resetIdle
  const [needsInteraction, setNeedsInteraction] = useState(false);

  // Subtitles
  const [subtitleTracks, setSubtitleTracks] = useState<SubtitleTrack[]>([]);
  const [activeSubtitleTrack, setActiveSubtitleTrack] = useState(-1);
  const [showSubtitlePicker, setShowSubtitlePicker] = useState(false);

  // Fullscreen
  const [isFullscreen, setIsFullscreen] = useState(false);
  useEffect(() => {
    const handler = () => setIsFullscreen(!!document.fullscreenElement);
    document.addEventListener("fullscreenchange", handler);
    return () => document.removeEventListener("fullscreenchange", handler);
  }, []);

  // Hide the shared nav bar in fullscreen; reveal it when overlays are shown
  // (mouse move / key press). Classes are cleaned up on unmount.
  useEffect(() => {
    document.body.classList.toggle("tv-fullscreen", isFullscreen);
    document.body.classList.toggle("tv-overlays", isFullscreen && showOverlays);
    return () => document.body.classList.remove("tv-fullscreen", "tv-overlays");
  }, [isFullscreen, showOverlays]);

  const toggleFullscreen = useCallback(() => {
    // Standard Fullscreen API (Chrome, Firefox, Safari desktop)
    if (document.fullscreenEnabled) {
      if (!document.fullscreenElement) {
        document.documentElement.requestFullscreen().catch(() => {});
      } else {
        document.exitFullscreen().catch(() => {});
      }
      return;
    }
    // iOS Safari: requestFullscreen is not supported. Fall back to the video
    // element's proprietary webkit API instead.
    const video = videoRef.current as HTMLVideoElement & {
      webkitEnterFullscreen?: () => void;
      webkitExitFullscreen?: () => void;
      webkitDisplayingFullscreen?: boolean;
    };
    if (!video?.webkitEnterFullscreen) return;
    if (video.webkitDisplayingFullscreen) {
      video.webkitExitFullscreen?.();
    } else {
      video.webkitEnterFullscreen();
    }
  }, []);

  // Volume control
  const [volume, setVolume] = useState(1);      // 0.0 – 1.0
  const [isMuted, setIsMuted] = useState(false);
  const [showVolumeSlider, setShowVolumeSlider] = useState(false);
  useEffect(() => {
    if (!videoRef.current) return;
    videoRef.current.muted = isMuted;
    videoRef.current.volume = volume;
  }, [isMuted, volume]);
  const toggleMute = useCallback(() => setIsMuted((m) => !m), []);
  const VolumeIcon = isMuted || volume === 0 ? VolumeX : volume < 0.5 ? Volume1 : Volume2;

  const { castAvailable, isCasting, castDeviceName, requestCast, stopCasting } = useCast();

  // Auto-mute local video while casting, restore on cast end
  const prevMutedRef = useRef(false);
  useEffect(() => {
    if (isCasting) {
      prevMutedRef.current = isMuted;
      setIsMuted(true);
    } else {
      setIsMuted(prevMutedRef.current);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isCasting]);

  // Channel jump by number (e.g. press "1","4" → jump to ch 14 after 1.5 s)
  const [channelInput, setChannelInput] = useState("");
  const channelInputTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Touch-swipe state
  const touchStartY = useRef<number | null>(null);

  const queryClient = useQueryClient();

  // Tick for live progress calculation (every 30 s is fine for the progress bar)
  const [, setTick] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setTick((n) => n + 1), 30_000);
    return () => clearInterval(id);
  }, []);

  // Per-channel data
  const { data: broadcast, isLoading: isLoadingBroadcast, error: broadcastError } =
    useCurrentBroadcast(channel?.id ?? "", channelPassword);
  const blockPassword = broadcast?.slot.id ? blockPasswords[broadcast.slot.id] : undefined;
  const { data: epgSlots } = useEpg(channel?.id ?? "", undefined, undefined, channelPassword);
  const { data: streamUrl, error: streamUrlError } = useStreamUrl(
    channel?.id, token, broadcast?.slot.id, channelPassword, blockPassword,
  );

  // iOS Safari: track fullscreen state via webkit video element events.
  // Re-run when streamUrl changes so we catch the video element after it mounts.
  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;
    const onBegin = () => setIsFullscreen(true);
    const onEnd = () => setIsFullscreen(false);
    video.addEventListener("webkitbeginfullscreen", onBegin);
    video.addEventListener("webkitendfullscreen", onEnd);
    return () => {
      video.removeEventListener("webkitbeginfullscreen", onBegin);
      video.removeEventListener("webkitendfullscreen", onEnd);
    };
  }, [streamUrl]);

  // Show channel password modal when broadcast returns password_required
  useEffect(() => {
    if ((broadcastError as Error)?.message === "password_required") {
      setShowChannelPasswordModal(true);
    }
  }, [broadcastError]);

  // Show block password modal when stream URL fetch returns password_required
  useEffect(() => {
    if ((streamUrlError as Error)?.message === "password_required") {
      setShowBlockPasswordModal(true);
    }
  }, [streamUrlError]);

  // Clear transient states when a new slot is detected
  useEffect(() => {
    setStreamError(false);
    setVideoEnded(false);
  }, [broadcast?.slot.id]);

  // Reset subtitle state when channel or slot changes
  useEffect(() => {
    setSubtitleTracks([]);
    setActiveSubtitleTrack(-1);
    setShowSubtitlePicker(false);
  }, [channelIdx, broadcast?.slot.id]);

  // ------------------------------------------------------------------
  // Derived display values
  // ------------------------------------------------------------------

  const hasBroadcast = !!broadcast;
  const progress = hasBroadcast
    ? calcProgress(broadcast.slot.start_at, broadcast.slot.item.duration_secs)
    : 0;

  const scheduleSlots = toScheduleSlots(epgSlots ?? [], broadcast?.slot.id);
  const nextSlot = findNextSlot(epgSlots ?? [], broadcast?.slot.id);
  const showBanner = hasBroadcast && progress >= BANNER_THRESHOLD && !!nextSlot;

  // ------------------------------------------------------------------
  // Idle detection
  // ------------------------------------------------------------------

  const resetIdle = useCallback(() => {
    setShowOverlays(true);
    setNeedsInteraction(false);
    if (idleTimer.current) clearTimeout(idleTimer.current);
    idleTimer.current = setTimeout(() => {
      setShowOverlays(false);
      setShowVolumeSlider(false);
      setShowSubtitlePicker(false);
    }, IDLE_TIMEOUT_MS);
    // Resume playback if autoplay was blocked (e.g. on page refresh with no prior interaction)
    videoRef.current?.play().catch(() => {});
  }, []);

  useEffect(() => {
    resetIdle();
    return () => {
      if (idleTimer.current) clearTimeout(idleTimer.current);
    };
  }, [resetIdle]);

  // ------------------------------------------------------------------
  // Channel switching
  // ------------------------------------------------------------------

  const channelCount = channels?.length ?? 0;

  const prevChannel = useCallback(() => {
    switchChannel((channelIdx - 1 + Math.max(channelCount, 1)) % Math.max(channelCount, 1));
    resetIdle();
  }, [channelIdx, channelCount, switchChannel, resetIdle]);

  const nextChannel = useCallback(() => {
    switchChannel((channelIdx + 1) % Math.max(channelCount, 1));
    resetIdle();
  }, [channelIdx, channelCount, switchChannel, resetIdle]);

  const toggleSchedule = useCallback(() => {
    setShowSchedule((s) => !s);
    resetIdle();
  }, [resetIdle]);

  // ------------------------------------------------------------------
  // Keyboard shortcuts
  // ------------------------------------------------------------------

  useEffect(() => {
    const handleKey = (e: KeyboardEvent) => {
      if (
        e.target instanceof HTMLInputElement ||
        e.target instanceof HTMLTextAreaElement
      )
        return;

      switch (e.key) {
        case "ArrowUp":
        case "PageUp":
          e.preventDefault();
          nextChannel();
          break;
        case "ArrowDown":
        case "PageDown":
          e.preventDefault();
          prevChannel();
          break;
        case "g":
        case "G":
          toggleSchedule();
          break;
        case "f":
        case "F":
          toggleFullscreen();
          break;
        case "m":
        case "M":
          toggleMute();
          break;
        default: {
          if (e.key >= "0" && e.key <= "9") {
            setChannelInput((prev) => {
              const next = prev + e.key;
              if (channelInputTimer.current) clearTimeout(channelInputTimer.current);
              channelInputTimer.current = setTimeout(() => {
                const num = parseInt(next, 10);
                if (num >= 1 && num <= Math.max(channelCount, 1)) {
                  switchChannel(num - 1);
                  resetIdle();
                }
                setChannelInput("");
              }, 1500);
              return next;
            });
          }
        }
      }
    };

    window.addEventListener("keydown", handleKey);
    return () => {
      window.removeEventListener("keydown", handleKey);
      if (channelInputTimer.current) clearTimeout(channelInputTimer.current);
    };
  }, [nextChannel, prevChannel, toggleSchedule, toggleFullscreen, toggleMute, channelCount, switchChannel, resetIdle]);

  // ------------------------------------------------------------------
  // Touch swipe (swipe up = next channel, swipe down = prev channel)
  // ------------------------------------------------------------------

  const handleTouchStart = useCallback((e: React.TouchEvent) => {
    touchStartY.current = e.touches[0].clientY;
    resetIdle();
  }, [resetIdle]);

  const handleTouchEnd = useCallback((e: React.TouchEvent) => {
    if (touchStartY.current === null) return;
    const dy = touchStartY.current - e.changedTouches[0].clientY;
    touchStartY.current = null;
    if (Math.abs(dy) > 60) {
      if (dy > 0) nextChannel();
      else prevChannel();
    }
  }, [nextChannel, prevChannel]);

  // ------------------------------------------------------------------
  // Stream error recovery
  // ------------------------------------------------------------------

  const handleStreamError = useCallback(() => {
    setStreamError(true);
  }, []);

  const handleVideoEnded = useCallback(() => {
    setVideoEnded(true);
    // Immediately poll for the next broadcast instead of waiting up to 30 s.
    queryClient.invalidateQueries({ queryKey: ["broadcast", channel?.id] });
  }, [queryClient, channel?.id]);

  const handleRetry = useCallback(() => {
    // Bust the cached stream URL so it refetches with the current offset
    queryClient.invalidateQueries({
      queryKey: ["stream-url", channel?.id, broadcast?.slot.id],
    });
    setStreamError(false);
  }, [queryClient, channel?.id, broadcast?.slot.id]);

  const submitChannelPassword = useCallback((password: string) => {
    if (!channel) return;
    const next = { ...channelPasswords, [channel.id]: password };
    setChannelPasswords(next);
    try { localStorage.setItem("channel_passwords", JSON.stringify(next)); } catch {}
    setShowChannelPasswordModal(false);
    queryClient.invalidateQueries({ queryKey: ["broadcast", channel.id] });
  }, [channel, channelPasswords, queryClient]);

  const submitBlockPassword = useCallback((password: string) => {
    if (!broadcast?.slot.id) return;
    const next = { ...blockPasswords, [broadcast.slot.id]: password };
    setBlockPasswords(next);
    try { localStorage.setItem("block_passwords", JSON.stringify(next)); } catch {}
    setShowBlockPasswordModal(false);
    queryClient.invalidateQueries({ queryKey: ["stream-url", channel?.id, broadcast.slot.id] });
  }, [broadcast?.slot.id, blockPasswords, channel?.id, queryClient]);

  // ------------------------------------------------------------------
  // Render helpers
  // ------------------------------------------------------------------

  const renderBase = () => {
    if (isLoadingChannels) {
      return <NoSignal variant="loading" message="Tuning in…" />;
    }
    if (!channels || channels.length === 0) {
      return (
        <NoSignal
          variant="no-signal"
          message="No channels configured. Visit the Dashboard to create one."
        />
      );
    }

    // Channel-level access errors (not password — those show a modal)
    const broadcastErrMsg = (broadcastError as Error)?.message;
    if (broadcastErrMsg === "auth_required") {
      return <NoSignal variant="locked" message="Sign in to watch this channel." />;
    }
    if (broadcastErrMsg && broadcastError && (broadcastError as { status?: number }).status === 403) {
      return <NoSignal variant="locked" message="This channel is owner-only." />;
    }

    if (isLoadingBroadcast) {
      return <NoSignal variant="loading" message="Tuning in…" />;
    }
    if (!hasBroadcast) {
      return (
        <NoSignal
          variant="no-signal"
          message="Nothing is scheduled right now. Check back later."
        />
      );
    }

    // Block-level access errors (not password — those show a modal overlay)
    const streamErrMsg = (streamUrlError as Error)?.message;
    if (streamErrMsg === "auth_required") {
      return <NoSignal variant="locked" message="Sign in to watch this block." />;
    }
    if (streamUrlError && (streamUrlError as { status?: number }).status === 403) {
      return <NoSignal variant="locked" message="This block is owner-only." />;
    }

    if (streamError) {
      return (
        <NoSignal variant="error" message="Stream failed to load.">
          <button
            onClick={handleRetry}
            className="mt-2 rounded-md border border-zinc-700 bg-zinc-800/80 px-4 py-2 text-xs text-zinc-300 transition-colors hover:bg-zinc-700 hover:text-zinc-100"
          >
            Retry
          </button>
        </NoSignal>
      );
    }
    if (videoEnded) {
      return <NoSignal variant="loading" message="Up next, stay tuned…" />;
    }
    if (streamUrl) {
      return (
        <VideoPlayer
          ref={videoRef}
          src={streamUrl}
          className="absolute inset-0 h-full w-full"
          initialOffset={broadcast ? calcOffsetSecs(broadcast.slot.start_at) : 0}
          subtitleTrack={activeSubtitleTrack}
          muted={isMuted}
          onSubtitleTracksChange={setSubtitleTracks}
          onStreamError={handleStreamError}
          onEnded={handleVideoEnded}
          onNeedsInteraction={() => setNeedsInteraction(true)}
        />
      );
    }
    // Broadcast exists but stream URL resolving — show loading until ready
    return <NoSignal variant="loading" message="Loading stream…" />;
  };

  // ------------------------------------------------------------------
  // Render
  // ------------------------------------------------------------------

  return (
    <div
      className="relative flex flex-1 overflow-hidden bg-black"
      style={{ cursor: showOverlays ? "default" : "none" }}
      onMouseMove={resetIdle}
      onClick={resetIdle}
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
    >
      {/* ── Base layer ─────────────────────────────────────────────── */}
      <div className="absolute inset-0">{renderBase()}</div>

      {/* ── Logo watermark — always visible, not tied to idle state ── */}
      {channel?.logo && (
        <div
          className={`pointer-events-none absolute z-10 p-3 ${logoPositionClass(channel.logo_position)}`}
          style={{ opacity: channel.logo_opacity ?? 1 }}
        >
          {channel.logo.trimStart().startsWith("<") ? (
            <div dangerouslySetInnerHTML={{ __html: channel.logo }} className="h-12 w-auto" />
          ) : (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={channel.logo} alt="" className="h-12 w-auto object-contain" />
          )}
        </div>
      )}

      {/* ── Channel password modal ──────────────────────────────────── */}
      {showChannelPasswordModal && (
        <ChannelPasswordModal
          label="Channel password required"
          onSubmit={submitChannelPassword}
          onCancel={() => setShowChannelPasswordModal(false)}
        />
      )}

      {/* ── Block password modal ────────────────────────────────────── */}
      {showBlockPasswordModal && (
        <ChannelPasswordModal
          label="Block password required"
          onSubmit={submitBlockPassword}
          onCancel={() => setShowBlockPasswordModal(false)}
        />
      )}

      {/* ── Autoplay blocked prompt ─────────────────────────────────── */}
      {needsInteraction && (
        <div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center">
          <div className="rounded-xl bg-black/70 px-8 py-5 text-center backdrop-blur-sm">
            <p className="text-sm font-medium text-zinc-200">Click or move the mouse to play</p>
          </div>
        </div>
      )}

      {/* ── Overlays — only when channels available ─────────────────── */}
      {channel && (
        <>
          <div
            className="pointer-events-none absolute inset-0 z-10 flex flex-col justify-between transition-opacity duration-300"
            style={{ opacity: showOverlays ? 1 : 0 }}
          >
            {/* Top-right: subtitle picker + guide toggle */}
            <div className="flex justify-end gap-2 p-4">
              {subtitleTracks.length > 0 && (
                <div className="pointer-events-auto relative">
                  <button
                    className="rounded-md bg-black/50 px-3 py-1.5 text-xs backdrop-blur transition-colors hover:bg-black/70 hover:text-white"
                    style={{
                      color: activeSubtitleTrack !== -1 ? "white" : undefined,
                      borderBottom:
                        activeSubtitleTrack !== -1
                          ? "2px solid white"
                          : "2px solid transparent",
                    }}
                    onClick={() => setShowSubtitlePicker((s) => !s)}
                  >
                    CC
                  </button>

                  {showSubtitlePicker && (
                    <div className="absolute right-0 top-9 z-30 min-w-[10rem] overflow-hidden rounded-md border border-zinc-700 bg-zinc-900/95 py-1 shadow-xl backdrop-blur">
                      <button
                        className={`w-full px-3 py-1.5 text-left text-xs transition-colors hover:bg-zinc-700 ${activeSubtitleTrack === -1 ? "text-white" : "text-zinc-400"}`}
                        onClick={() => {
                          setActiveSubtitleTrack(-1);
                          setShowSubtitlePicker(false);
                        }}
                      >
                        Off
                      </button>
                      {subtitleTracks.map((track) => (
                        <button
                          key={track.id}
                          className={`w-full px-3 py-1.5 text-left text-xs transition-colors hover:bg-zinc-700 ${activeSubtitleTrack === track.id ? "text-white" : "text-zinc-400"}`}
                          onClick={() => {
                            setActiveSubtitleTrack(track.id);
                            setShowSubtitlePicker(false);
                          }}
                        >
                          {track.name || track.lang || `Track ${track.id + 1}`}
                        </button>
                      ))}
                    </div>
                  )}
                </div>
              )}

              {/* Volume control */}
              <div className="pointer-events-auto relative">
                <button
                  className="rounded-md bg-black/50 p-1.5 text-zinc-400 backdrop-blur transition-colors hover:bg-black/70 hover:text-white"
                  onClick={() => setShowVolumeSlider((s) => !s)}
                  title="Volume"
                >
                  <VolumeIcon className="h-4 w-4" />
                </button>

                {showVolumeSlider && (
                  <div className="absolute right-0 top-9 z-30 w-36 rounded-lg border border-zinc-700 bg-zinc-900/95 p-3 shadow-xl backdrop-blur">
                    <input
                      type="range"
                      min={0}
                      max={100}
                      value={isMuted ? 0 : Math.round(volume * 100)}
                      onChange={(e) => {
                        const v = Number(e.target.value) / 100;
                        setVolume(v);
                        setIsMuted(v === 0);
                      }}
                      className="w-full accent-white"
                    />
                    <div className="mt-1.5 flex items-center justify-between">
                      <button
                        onClick={toggleMute}
                        className="text-[10px] text-zinc-500 hover:text-zinc-300"
                      >
                        {isMuted ? "Unmute [M]" : "Mute [M]"}
                      </button>
                      <span className="font-mono text-[10px] text-zinc-500">
                        {isMuted ? "0" : Math.round(volume * 100)}%
                      </span>
                    </div>
                  </div>
                )}
              </div>

              <button
                className="pointer-events-auto rounded-md bg-black/50 p-1.5 text-zinc-400 backdrop-blur transition-colors hover:bg-black/70 hover:text-white"
                onClick={toggleFullscreen}
                title={isFullscreen ? "Exit fullscreen [F]" : "Fullscreen [F]"}
              >
                {isFullscreen
                  ? <Minimize2 className="h-4 w-4" />
                  : <Maximize2 className="h-4 w-4" />}
              </button>

              {castAvailable && (
                <button
                  className={`pointer-events-auto rounded-md bg-black/50 p-1.5 backdrop-blur transition-colors hover:bg-black/70 hover:text-white ${
                    isCasting ? "text-blue-400" : "text-zinc-400"
                  }`}
                  onClick={() => isCasting ? stopCasting() : streamUrl && requestCast(streamUrl)}
                  title={isCasting ? `Stop casting to ${castDeviceName ?? "TV"}` : "Cast to TV"}
                >
                  <Cast className="h-4 w-4" />
                </button>
              )}

              <button
                className="pointer-events-auto rounded-md bg-black/50 px-3 py-1.5 text-xs text-zinc-400 backdrop-blur transition-colors hover:bg-black/70 hover:text-white"
                onClick={toggleSchedule}
              >
                {showSchedule ? "Hide guide" : "Guide [G]"}
              </button>
            </div>

            {/* Bottom: banner + info row */}
            <div className="flex flex-col gap-3 bg-gradient-to-t from-black/80 via-black/40 to-transparent p-5 pt-20">
              {showBanner && nextSlot && (
                <UpNextBanner
                  nextShowTitle={nextSlot.item.title}
                  minutesUntil={minutesUntil(nextSlot.start_at)}
                  nextShowStartTime={fmtTime(nextSlot.start_at)}
                />
              )}

              <div className="flex items-end justify-between gap-4">
                {hasBroadcast ? (
                  <ChannelInfo
                    channelNumber={channelIdx + 1}
                    channelName={channel.name}
                    item={broadcast.slot.item}
                    showStartTime={fmtTime(broadcast.slot.start_at)}
                    showEndTime={fmtTime(broadcast.slot.end_at)}
                    progress={progress}
                  />
                ) : (
                  /* Minimal channel badge when no broadcast */
                  <div className="rounded-lg bg-black/60 px-4 py-3 backdrop-blur-md">
                    <div className="flex items-center gap-2">
                      <span className="flex h-7 min-w-9 items-center justify-center rounded bg-white px-1.5 font-mono text-xs font-bold text-black">
                        {channelIdx + 1}
                      </span>
                      <span className="text-sm font-medium text-zinc-300">
                        {channel.name}
                      </span>
                    </div>
                  </div>
                )}

                <div className="pointer-events-auto">
                  <ChannelControls
                    channelNumber={channelIdx + 1}
                    channelName={channel.name}
                    onPrevChannel={prevChannel}
                    onNextChannel={nextChannel}
                  />
                </div>
              </div>
            </div>
          </div>

          {/* Casting indicator */}
          {isCasting && castDeviceName && (
            <div className="pointer-events-none absolute left-4 top-4 z-10 rounded-md bg-black/60 px-3 py-1.5 text-xs text-blue-300 backdrop-blur">
              Casting to {castDeviceName}
            </div>
          )}

          {/* Channel number input overlay */}
          {channelInput && (
            <div className="pointer-events-none absolute left-1/2 top-1/2 z-30 -translate-x-1/2 -translate-y-1/2 rounded-xl bg-black/80 px-8 py-5 text-center backdrop-blur">
              <p className="mb-1 text-[10px] uppercase tracking-widest text-zinc-500">Channel</p>
              <p className="font-mono text-5xl font-bold text-white">{channelInput}</p>
            </div>
          )}

          {/* Schedule overlay — outside the fading div so it has its own visibility */}
          {showOverlays && showSchedule && (
            <div className="absolute bottom-4 right-4 top-14 z-20 w-80">
              <ScheduleOverlay
                channelName={channel.name}
                slots={scheduleSlots}
              />
            </div>
          )}
        </>
      )}
    </div>
  );
}
