// CRT television set. Wooden bezel, channel buttons, antenna, knobs.
const { useState, useEffect, useRef } = React;

// Renders feeds for prev / current / next channel simultaneously. Each
// channel uses HLS via <video>+hls.js when channel.hls is present
// (preferred: direct broadcaster, 24/7, no YouTube ads), or a YouTube
// live_stream <iframe> otherwise. All are muted+autoplay; only the
// active one is opaque + clickable, so a channel-up/down switch reveals
// an already-buffered stream.
function HlsVideo({ channel, active }) {
  const videoRef = useRef(null);
  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;
    const src = proxyHls(channel.hls);
    let hls = null;
    // Safari + iOS handle HLS natively.
    if (video.canPlayType("application/vnd.apple.mpegurl")) {
      video.src = src;
    } else if (window.Hls && window.Hls.isSupported()) {
      hls = new window.Hls({
        lowLatencyMode: true,
        backBufferLength: 30,
        maxBufferLength: 12,
      });
      hls.loadSource(src);
      hls.attachMedia(video);
    } else {
      // No HLS support and no native — leave the video element blank;
      // the channel will appear as a black screen behind the CRT overlay.
      console.warn("HLS unsupported in this browser:", channel.name);
    }
    return () => { if (hls) hls.destroy(); };
  }, [channel.hls]);
  return (
    <video
      ref={videoRef}
      className={`crt-feed ${active ? "active" : ""}`}
      autoPlay
      muted
      playsInline
    />
  );
}

function ChannelFeeds({ channels, activeNum, tuning }) {
  return (
    <div className="crt-feed-stack">
      {channels.map(c => {
        const active = c.num === activeNum && !tuning;
        if (c.hls) {
          return <HlsVideo key={c.num} channel={c} active={active} />;
        }
        if (c.ytId) {
          return (
            <iframe
              key={c.num}
              className={`crt-feed ${active ? "active" : ""}`}
              src={`https://www.youtube.com/embed/live_stream?channel=${c.ytId}&autoplay=1&mute=1&controls=0&modestbranding=1&rel=0&playsinline=1&cc_load_policy=1&cc_lang_pref=${c.lang || "en"}`}
              title={`${c.name} live`}
              allow="autoplay; encrypted-media; picture-in-picture"
              referrerPolicy="origin"
              frameBorder="0"
            />
          );
        }
        return null;
      })}
    </div>
  );
}

function CrtScreenContent({ channel, tuning }) {
  // Faux broadcast chrome (bug, time, lower-third). The actual video lives
  // in <ChannelFeeds /> which is rendered as a sibling beneath this layer.
  const [time, setTime] = useState(() => new Date());
  useEffect(() => {
    const t = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(t);
  }, []);
  const timeStr = time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });

  if (tuning) {
    return (
      <div className="crt-static">
        <div className="crt-static-noise" />
        <div className="crt-static-band" />
        <div className="crt-tuning-label">— NO SIGNAL —</div>
      </div>
    );
  }

  const liveFeed = !!channel.ytId;

  return (
    <div className={`crt-broadcast ${liveFeed ? "crt-has-feed" : ""}`} style={{ "--ch-hue": channel.hue }}>
      {!liveFeed && (
        <>
          <div className="crt-bg" />
          <div className="crt-anchor-block">
            <div className="crt-placeholder-tag">[ live anchor — {channel.region} studio ]</div>
          </div>
        </>
      )}
      <div className="crt-bug">
        <span className="crt-bug-name">{channel.name}</span>
        <span className="crt-bug-live"><span className="crt-bug-dot" />LIVE</span>
      </div>
      <div className="crt-time">{timeStr}</div>
      <div className="crt-lower-third">
        <div className="crt-lower-tag">{channel.tag}</div>
        <div className="crt-lower-headline">{channel.lowerThird}</div>
        <div className="crt-ticker">
          <div className="crt-ticker-track">
            {channel.tag} · {channel.lowerThird} · markets steady · weather: scattered cloud · sports: late results · {channel.tag} · {channel.lowerThird} ·
          </div>
        </div>
      </div>
      <div className="crt-channel-bug">CH {channel.num}</div>
      <div className={`crt-source-bug crt-source-${channel.hls ? "hls" : "yt"}`}>
        <span className="crt-source-type">{channel.hls ? "HLS" : "YT"}</span>
        <span className="crt-source-sep">·</span>
        <span className="crt-source-lang">{(channel.lang || "").toUpperCase()}</span>
        <span className="crt-source-sep">·</span>
        <span className="crt-source-region">{channel.region}</span>
      </div>
    </div>
  );
}

function CrtCaption({ text, status }) {
  // Custom lower-third for client-side Whisper captions. Renders the
  // most recent transcribed line above the existing news ticker.
  // `status` drives the small badge in the top-left while the model
  // downloads / initializes / errors out.
  if (!text && (!status || status.kind === "active")) return null;
  return (
    <>
      {status && status.kind !== "active" && (
        <div className={`crt-caption-status crt-caption-status-${status.kind}`}>
          {status.message}
        </div>
      )}
      {text && (
        <div className="crt-caption-track">
          <div className="crt-caption-text">{text}</div>
        </div>
      )}
    </>
  );
}

function CrtScreen({ channel, tuning, showBanner, feeds, captionText, captionStatus }) {
  return (
    <div className="crt-screen-outer">
      <div className="crt-screen">
        <div className="crt-screen-inner">
          <ChannelFeeds channels={feeds} activeNum={channel.num} tuning={tuning} />
          <CrtScreenContent channel={channel} tuning={tuning} />
          {!tuning && <CrtCaption text={captionText} status={captionStatus} />}
          {showBanner && !tuning && (
            <div className="crt-banner">
              <div className="crt-banner-num">{channel.num}</div>
              <div className="crt-banner-info">
                <div className="crt-banner-name">{channel.name}</div>
                <div className="crt-banner-sub">{channel.sub} · {channel.region}</div>
              </div>
            </div>
          )}
          <div className="crt-scanlines" />
          <div className="crt-vignette" />
          <div className="crt-glow" />
        </div>
      </div>
    </div>
  );
}

function TvSet({ skin = "wood", langFilter = "all" }) {
  const [idx, setIdx] = useState(0);
  const [tuning, setTuning] = useState(false);
  const [banner, setBanner] = useState(true);
  const [vol, setVol] = useState(0.55);
  const [keypadBuf, setKeypadBuf] = useState("");
  const [cc, setCc] = useState(false);
  const [captionText, setCaptionText] = useState("");
  const [captionStatus, setCaptionStatus] = useState(null);
  const tuneTimer = useRef(null);
  const bannerTimer = useRef(null);

  // Clear stale caption text when the channel changes (or captions turn
  // off) so we don't briefly show the previous channel's transcript on
  // the new channel.
  useEffect(() => {
    setCaptionText("");
    if (!cc) setCaptionStatus(null);
  }, [idx, cc]);

  const ch = TV_CHANNELS[idx];
  const len = TV_CHANNELS.length;

  // When a language filter is active, walk the channel list in the
  // requested direction until we hit a match. Falls back to the original
  // index if nothing matches (= filter on a language with zero channels).
  function findInDir(fromIdx, direction) {
    if (langFilter === "all") {
      return ((fromIdx + direction) % len + len) % len;
    }
    let i = fromIdx;
    for (let step = 0; step < len; step++) {
      i = ((i + direction) % len + len) % len;
      if (TV_CHANNELS[i].lang === langFilter) return i;
    }
    return fromIdx;
  }

  // If the user flips the filter and we're sitting on a channel that no
  // longer matches, jump to the first matching one. Keypad lookups
  // (which target a specific channel.num) bypass the filter intentionally.
  useEffect(() => {
    if (langFilter === "all") return;
    if (ch.lang === langFilter) return;
    const targetIdx = TV_CHANNELS.findIndex(c => c.lang === langFilter);
    if (targetIdx >= 0 && targetIdx !== idx) changeChannel(targetIdx);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [langFilter]);

  // Warm-feed window: previous, current, next channel *within* the filter
  // (so we don't preload streams the user can't get to). Deduped so
  // narrow filters don't crash, and React reuses iframe DOM nodes across
  // switches because each is keyed by channel.num.
  const prevIdx = findInDir(idx, -1);
  const nextIdx = findInDir(idx, +1);
  const feeds = Array.from(
    new Map([prevIdx, idx, nextIdx].map(i => [i, TV_CHANNELS[i]])).values()
  );

  function changeChannel(nextIdx) {
    const n = ((nextIdx % len) + len) % len;
    if (n === idx) return;
    setTuning(true);
    setBanner(false);
    clearTimeout(tuneTimer.current);
    clearTimeout(bannerTimer.current);
    // Short cosmetic static; the iframe behind it is already playing.
    tuneTimer.current = setTimeout(() => {
      setIdx(n);
      setTuning(false);
      setBanner(true);
      bannerTimer.current = setTimeout(() => setBanner(false), 2400);
    }, 180);
  }

  function pressKey(d) {
    const buf = (keypadBuf + d).slice(-2);
    setKeypadBuf(buf);
    if (buf.length === 2) {
      const target = TV_CHANNELS.findIndex(c => c.num === buf);
      if (target >= 0) changeChannel(target);
      setTimeout(() => setKeypadBuf(""), 600);
    }
  }

  useEffect(() => {
    function onKey(e) {
      // Don't hijack typing in form controls.
      const tag = (e.target && e.target.tagName || "").toLowerCase();
      if (tag === "input" || tag === "textarea" || (e.target && e.target.isContentEditable)) return;
      const isArrow = e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight";
      const isDigit = /^[0-9]$/.test(e.key);
      if (isArrow || isDigit) e.preventDefault();
      if (e.key === "ArrowUp" || e.key === "ArrowRight") changeChannel(findInDir(idx, +1));
      if (e.key === "ArrowDown" || e.key === "ArrowLeft") changeChannel(findInDir(idx, -1));
      if (isDigit) pressKey(e.key);
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [idx, keypadBuf, langFilter]);

  return (
    <div className={`tv-set tv-skin-${skin}`}>
      <div className="tv-antenna">
        <div className="tv-antenna-rod tv-antenna-left" />
        <div className="tv-antenna-rod tv-antenna-right" />
        <div className="tv-antenna-base" />
      </div>
      <div className="tv-cabinet">
        <div className="tv-bezel">
          <CrtScreen
            channel={ch}
            tuning={tuning}
            showBanner={banner}
            feeds={feeds}
            captionText={cc && ch.hls ? captionText : ""}
            captionStatus={cc && ch.hls ? captionStatus : null}
          />
          {cc && ch.hls && (
            <HlsCaptioner
              channel={ch}
              onText={setCaptionText}
              onStatus={setCaptionStatus}
            />
          )}
          <div className="tv-badge">CHANNELROAM · 24"</div>
        </div>
        <div className="tv-control-panel">
          <div className="tv-led-display">
            <div className="tv-led-num">{keypadBuf || ch.num}</div>
            <div className="tv-led-label">CH</div>
          </div>
          <div className="tv-keypad">
            {["1","2","3","4","5","6","7","8","9","0"].map(d => (
              <button key={d} className="tv-key" onClick={() => pressKey(d)}>{d}</button>
            ))}
          </div>
          <div className="tv-rocker">
            <button className="tv-rocker-btn" onClick={() => changeChannel(findInDir(idx, +1))} aria-label="Channel up">
              <svg viewBox="0 0 16 16"><path d="M8 4 L13 11 L3 11 Z" fill="currentColor"/></svg>
            </button>
            <div className="tv-rocker-label">CH</div>
            <button className="tv-rocker-btn" onClick={() => changeChannel(findInDir(idx, -1))} aria-label="Channel down">
              <svg viewBox="0 0 16 16"><path d="M8 12 L13 5 L3 5 Z" fill="currentColor"/></svg>
            </button>
          </div>
          <div className="tv-cc-cluster">
            <button
              className={`tv-cc-btn ${cc ? "on" : ""} ${!ch.hls ? "tv-cc-btn-disabled" : ""}`}
              onClick={() => {
                if (!ch.hls) return; // only HLS channels can be captioned
                setCc(c => !c);
              }}
              title={ch.hls
                ? (cc ? "Disable live captions" : "Enable live captions (Whisper in browser)")
                : "Captions are only available on HLS channels (green HLS badge)"}
              aria-label="Toggle captions"
            >
              CC
            </button>
            <div className="tv-knob-label">CAPS</div>
          </div>
          <div className="tv-knob-group">
            <div className="tv-knob" style={{ "--rot": `${-135 + vol * 270}deg` }}
                 onWheel={(e) => { e.preventDefault(); setVol(v => Math.max(0, Math.min(1, v + (e.deltaY < 0 ? 0.05 : -0.05)))); }}>
              <div className="tv-knob-dial" />
              <div className="tv-knob-tick" />
            </div>
            <div className="tv-knob-label">VOL</div>
          </div>
        </div>
      </div>
      <div className="tv-legs">
        <div className="tv-leg" />
        <div className="tv-leg" />
      </div>
    </div>
  );
}

Object.assign(window, { TvSet });
