// app.jsx — Homechella prototype.
//
// Uses pixel-ui.jsx for chrome, schedule.js for data + time helpers,
// tweaks-panel.jsx for the Tweaks panel.

const { useEffect, useRef, useState, useMemo, useCallback } = React;

// ─────────────────────────────────────────────────────────────────────────────
// Defaults persisted via the Tweaks host protocol.

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "auto",
  "crtIntensity": "medium",
  "showChat": true,
  "chatDensity": "lively",
  "simulateTime": false,
  "simHour": 20,
  "simMinute": 45,
  "muted": false,
  "showScanlines": true
}/*EDITMODE-END*/;

const THEMES = {
  "neon-night":  { bg: "#0d0420", mid: "#2a0e4d", glow: "#ff5cf2", accent: "#7be0ff", sun: "#ffd23f", horizon: "#ff5cf2" },
  "desert-dusk": { bg: "#1a0510", mid: "#4a1626", glow: "#ff8a3d", accent: "#ffd23f", sun: "#ff5cf2", horizon: "#ff8a3d" },
  "daylight":    { bg: "#082040", mid: "#1c4a8f", glow: "#ffd23f", accent: "#7be0ff", sun: "#ffd23f", horizon: "#7be0ff" },
};

// Auto-shift by PST hour:
//  06:00 → daylight
//  16:00 → desert-dusk
//  20:00 → neon-night (back through midnight to 06:00)
function resolveAutoTheme(H) {
  if (H >= 6 && H < 16) return "daylight";
  if (H >= 16 && H < 20) return "desert-dusk";
  return "neon-night";
}

const THEME_LABEL = {
  "daylight": "DAYLIGHT",
  "desert-dusk": "DESERT DUSK",
  "neon-night": "NEON NIGHT",
};

// ─────────────────────────────────────────────────────────────────────────────
// "Now" — returns hcPstParts for either real PST or simulated time.

function useNow(simulated, simH, simM) {
  const [tick, setTick] = useState(() => Date.now());
  useEffect(() => {
    const id = setInterval(() => setTick(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);
  return useMemo(() => {
    if (simulated) {
      // Build a fake hcPstParts where wallSec is derived from sim hour/minute,
      // but seconds advance with real elapsed time so video stays in sync.
      const baseSec = simH * 3600 + simM * 60;
      const driftSec = Math.floor((tick / 1000) % 60);
      const wallSec = baseSec + driftSec;
      const H = Math.floor(wallSec / 3600) % 24;
      const M = Math.floor((wallSec % 3600) / 60);
      const S = wallSec % 60;
      return { H, M, S, wallSec: wallSec % (24 * 3600), weekday: "SAT", y: 2026, mo: 5, d: 12, dateKey: "2026-05-12", simulated: true };
    }
    return window.hcPstParts(new Date(tick));
  }, [tick, simulated, simH, simM]);
}

// ─────────────────────────────────────────────────────────────────────────────
// Fake chat — pool of messages, varies by channel.

const CHAT_NAMES = [
  "raveking_88", "pixelP4nther", "sonic_b00m", "lo-fi_lottie", "synth_kid", "DJtreebark",
  "moonflw", "festivore", "neon_nelly", "wavve.dasha", "starboi_42", "groovewitch",
  "16bit_brody", "amplify_ari", "tunemoth", "freqfox", "discoll4ma", "pavlova",
  "subwooofer", "bossbeats_22", "kicksnare", "echo.exe", "vibetide",
];

const CHAT_GENERIC = [
  "this is INSANE 🔥", "set of the festival", "wait the drop", "okay okay OKAY",
  "no thoughts head empty", "the lights omg", "i can hear my heartbeat in this one",
  "is anyone else crying", "stop they're so back", "needed this", "ATE", "10/10",
  "where's the camera op going pls", "i flew in for this exact set", "encore?? ENCORE",
  "front row is shaking", "this is what hi-fi sounds like", "the bass.", "they got us",
  "if you tuned out you're missing it", "screenshotting every second",
  "homechella >>> real chella ngl", "🤘🤘🤘", "<3 <3 <3", "yelling rn", "this song saved me in 2019",
  "no skips", "the venn diagram is a circle", "OPENING WITH THIS???", "BYE",
  "literally what",
];

const CHAT_BETWEEN = [
  "stage swap who's next", "anyone got the time", "snack run brb", "channel surfing",
  "be right back making nachos", "lineup looking STACKED", "rinsed",
];

function makeChatLine(channel, artist, state) {
  const u = CHAT_NAMES[Math.floor(Math.random() * CHAT_NAMES.length)];
  let text;
  const r = Math.random();
  if (state === "live") {
    if (r < 0.18 && artist) text = `${artist} ${["forever", "I love u", "is everything", "literally never recovering"][Math.floor(Math.random()*4)]}`;
    else if (r < 0.35) text = `[ch ${channel.no}] ${CHAT_GENERIC[Math.floor(Math.random()*CHAT_GENERIC.length)]}`;
    else text = CHAT_GENERIC[Math.floor(Math.random() * CHAT_GENERIC.length)];
  } else {
    text = CHAT_BETWEEN[Math.floor(Math.random() * CHAT_BETWEEN.length)];
  }
  return { u, text, id: Math.random().toString(36).slice(2), color: channel.accent };
}

// Generate or retrieve a persistent username for this viewer.
function getViewerUsername() {
  const key = "hc:username";
  const stored = localStorage.getItem(key);
  if (stored) return stored;
  const adjectives = ["pixel", "neon", "retro", "bass", "synth", "lo_fi", "wave", "sonic", "vibe", "echo"];
  const nouns = ["fan", "kid", "wolf", "moth", "fox", "bird", "star", "crew", "bot", "ghost"];
  const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
  const noun = nouns[Math.floor(Math.random() * nouns.length)];
  const num = Math.floor(Math.random() * 90) + 10;
  const name = `${adj}_${noun}${num}`;
  localStorage.setItem(key, name);
  return name;
}

function ChatPanel({ channel, slot, state, density }) {
  const [msgs, setMsgs] = useState([]);
  const [inputVal, setInputVal] = useState("");
  const scrollRef = useRef(null);
  const inputRef = useRef(null);
  const intervalRef = useRef(800);
  const viewerName = useRef(getViewerUsername());

  useEffect(() => {
    intervalRef.current = density === "quiet" ? 2400 : density === "lively" ? 900 : density === "off" ? 9e9 : 1500;
  }, [density]);

  useEffect(() => {
    // Reset on channel change but keep going.
    setMsgs(Array.from({ length: 8 }).map(() => makeChatLine(channel, slot?.artist, state)));
  }, [channel.id]);

  useEffect(() => {
    if (density === "off") return;
    let cancelled = false;
    const tick = () => {
      if (cancelled) return;
      setMsgs((m) => {
        const next = [...m, makeChatLine(channel, slot?.artist, state)];
        return next.length > 50 ? next.slice(-50) : next;
      });
      const jitter = 0.5 + Math.random();
      setTimeout(tick, intervalRef.current * jitter);
    };
    const t = setTimeout(tick, intervalRef.current);
    return () => { cancelled = true; clearTimeout(t); };
  }, [channel.id, slot?.artist, state, density]);

  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    el.scrollTop = el.scrollHeight;
  }, [msgs]);

  const sendMessage = useCallback(() => {
    const text = inputVal.trim();
    if (!text) return;
    const msg = {
      u: viewerName.current,
      text,
      id: Math.random().toString(36).slice(2),
      color: channel.accent,
      mine: true,
    };
    setMsgs((m) => {
      const next = [...m, msg];
      return next.length > 50 ? next.slice(-50) : next;
    });
    setInputVal("");
    inputRef.current?.focus();
  }, [inputVal, channel.accent]);

  const handleKeyDown = useCallback((e) => {
    if (e.key === "Enter") sendMessage();
  }, [sendMessage]);

  if (density === "off") return null;

  return (
    <aside className="hc-chat">
      <header className="hc-chat-hd">
        <span className="hc-chat-dot" />
        <span className="hc-chat-title">LIVE CHAT</span>
        <span className="hc-chat-count">{(247 + msgs.length * 3).toLocaleString()} watching</span>
      </header>
      <div className="hc-chat-feed" ref={scrollRef}>
        {msgs.map((m) => (
          <div key={m.id} className={`hc-chat-line${m.mine ? " hc-chat-line--mine" : ""}`}>
            <span className="hc-chat-u" style={{ color: m.mine ? "#fff" : m.color }}>
              {m.u}{m.mine && <span className="hc-chat-you"> (you)</span>}
            </span>
            <span className="hc-chat-t">{m.text}</span>
          </div>
        ))}
      </div>
      <footer className="hc-chat-ft">
        <input
          className="hc-chat-input"
          placeholder="SAY SOMETHING…"
          value={inputVal}
          onChange={(e) => setInputVal(e.target.value)}
          onKeyDown={handleKeyDown}
          ref={inputRef}
          maxLength={200}
        />
        <button className="hc-chat-send" onClick={sendMessage}>SEND</button>
      </footer>
    </aside>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Reaction layer hook

function useReactions() {
  const [bursts, setBursts] = useState([]);
  const idRef = useRef(0);
  const burst = useCallback((emoji, color) => {
    const id = ++idRef.current;
    const newBurst = {
      id,
      emoji,
      color: color || "#fff",
      x: 30 + Math.random() * 40,
      dur: 2 + Math.random() * 1.5,
    };
    setBursts((b) => [...b, newBurst]);
    setTimeout(() => {
      setBursts((b) => b.filter((x) => x.id !== id));
    }, newBurst.dur * 1000);
  }, []);
  return [bursts, burst];
}

// ─────────────────────────────────────────────────────────────────────────────
// CRT power-on intro

function CRTIntro({ onDone }) {
  const [phase, setPhase] = useState("dark"); // dark → flash → scan → logo → done
  useEffect(() => {
    const t1 = setTimeout(() => setPhase("flash"), 250);
    const t2 = setTimeout(() => setPhase("scan"), 500);
    const t3 = setTimeout(() => setPhase("logo"), 1200);
    const t4 = setTimeout(() => { setPhase("done"); onDone(); }, 3200);
    return () => { [t1,t2,t3,t4].forEach(clearTimeout); };
  }, []);
  return (
    <div className={`hc-intro hc-intro--${phase}`}>
      <div className="hc-intro-flash" />
      <div className="hc-intro-scan" />
      <div className="hc-intro-logo">
        <HomechellaLogo big />
        <div className="hc-intro-sub">◀ ◀ TUNING IN ▶ ▶</div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Wordmark — chunky pixel logo from CSS

function HomechellaLogo({ big }) {
  return (
    <div className={`hc-logo ${big ? "hc-logo--big" : ""}`}>
      <span className="hc-logo-1">HOME</span>
      <span className="hc-logo-2">CHELLA</span>
      <span className="hc-logo-tag">▼ 16-BIT FESTIVAL BROADCAST ▼</span>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Lineup poster — backdrop scene + grid of channel tiles

function PosterBackdrop({ theme }) {
  const T = THEMES[theme] || THEMES["neon-night"];
  return (
    <div className="hc-poster-bg" style={{
      "--bg": T.bg, "--mid": T.mid, "--glow": T.glow,
      "--accent": T.accent, "--sun": T.sun, "--horizon": T.horizon,
    }}>
      <div className="hc-stars" />
      <div className="hc-sun-wrap">
        <ChannelGlyph kind="sun" scale={6} />
      </div>
      <div className="hc-horizon" />
      <div className="hc-ground" />
      <div className="hc-palm hc-palm--l"><ChannelGlyph kind="palm" scale={10} /></div>
      <div className="hc-palm hc-palm--r"><ChannelGlyph kind="palm" scale={10} /></div>
    </div>
  );
}

function ChannelRow({ channel, resolved, onClick }) {
  const live = resolved.state === "live";
  const next = resolved.state === "soon";
  const slot = resolved.slot;
  const upcoming = resolved.nextSlot;
  return (
    <button className={`hc-row ${live ? "is-live" : next ? "is-soon" : "is-dark"}`}
      style={{ "--accent": channel.accent }}
      onClick={onClick}
    >
      <div className="hc-row-ch">
        <span className="hc-row-chnum">CH {String(channel.no).padStart(2, "0")}</span>
        {live && <span className="hc-row-onair"><span className="hc-row-onair-dot" />ON AIR</span>}
      </div>
      <div className="hc-row-icon">
        <ChannelGlyph kind={channel.glyph} scale={3} />
      </div>
      <div className="hc-row-info">
        <div className="hc-row-name">{channel.name}</div>
        <div className="hc-row-stage">{channel.stage}</div>
      </div>
      <div className="hc-row-now">
        {live && slot && (
          <>
            <span className="hc-row-now-l">NOW PLAYING</span>
            <span className="hc-row-now-a">{slot.artist}</span>
          </>
        )}
        {next && upcoming && (
          <>
            <span className="hc-row-now-l">NEXT @ {window.hcFormat12h(upcoming.time)}</span>
            <span className="hc-row-now-a">{upcoming.artist}</span>
          </>
        )}
        {!live && !next && upcoming && (
          <>
            <span className="hc-row-now-l">TOMORROW</span>
            <span className="hc-row-now-a">{upcoming.artist}</span>
          </>
        )}
        {!live && !next && !upcoming && (
          <span className="hc-row-now-l">SIGNED OFF</span>
        )}
      </div>
      <div className="hc-row-tune">
        <span className="hc-row-tune-btn">▶ TUNE IN</span>
      </div>
    </button>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Full Lineup Overlay — retro TV guide grid

const MS_PER_PX = 60000 / 2; // 2px per minute

function LineupOverlay({ channels, now, onClose }) {
  const scrollRef = useRef(null);

  useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, []);

  // 12-hour window anchored to real UTC timestamps
  const nowMs      = Date.now();
  const winStartMs = nowMs - 60 * 60 * 1000;        // 1h before now
  const winEndMs   = nowMs + 11 * 60 * 60 * 1000;   // 11h after now
  const totalW     = Math.floor((winEndMs - winStartMs) / MS_PER_PX);
  const nowX       = Math.floor((nowMs - winStartMs) / MS_PER_PX);

  useEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollLeft = Math.max(0, nowX - el.clientWidth * 0.2);
  }, []);

  useEffect(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [onClose]);

  // Time labels every 30 min aligned to real timestamps
  const timeLabels = [];
  const step = 30 * 60 * 1000;
  let labelMs = Math.ceil(winStartMs / step) * step;
  while (labelMs <= winEndMs) {
    const p = window.hcPstParts(new Date(labelMs));
    const h12 = ((p.H + 11) % 12) + 1;
    const ampm = p.H >= 12 ? "PM" : "AM";
    timeLabels.push({
      label: `${h12}:${String(p.M).padStart(2,"0")} ${ampm}`,
      x: Math.floor((labelMs - winStartMs) / MS_PER_PX),
    });
    labelMs += step;
  }

  return (
    <div className="hc-guide-back" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="hc-guide">
        <div className="hc-guide-header">
          <span className="hc-guide-title">◼ FULL LINEUP</span>
          <span className="hc-guide-clock">{window.hcFormatClock(now)}</span>
          <button className="overlay-close" onClick={onClose}>✕</button>
        </div>

        <div className="hc-guide-scroll" ref={scrollRef}>
          <div className="hc-guide-row hc-guide-timehead">
            <div className="hc-guide-chcol" />
            <div className="hc-guide-track" style={{ width: totalW, position: "relative", height: 28 }}>
              {timeLabels.map((t, i) => (
                <span key={i} className="hc-guide-timelabel" style={{ left: t.x }}>{t.label}</span>
              ))}
              <div className="hc-guide-nowline" style={{ left: nowX }} />
            </div>
          </div>

          {channels.map(ch => (
            <div key={ch.id} className="hc-guide-row">
              <div className="hc-guide-chcol" style={{ color: ch.accent }}>{ch.name}</div>
              <div className="hc-guide-track" style={{ width: totalW, position: "relative" }}>
                <div className="hc-guide-nowline" style={{ left: nowX }} />
                {(ch.lineup || []).filter(s => s.startMs).map((slot, i) => {
                  const cellStartMs = slot.startMs;
                  const cellEndMs   = slot.startMs + slot.duration * 60 * 1000;
                  if (cellEndMs <= winStartMs || cellStartMs >= winEndMs) return null;
                  const left  = Math.max(0, Math.floor((cellStartMs - winStartMs) / MS_PER_PX));
                  if (left >= totalW) return null;
                  const right = Math.min(totalW, Math.floor((cellEndMs - winStartMs) / MS_PER_PX));
                  const w     = Math.min(right - left - 2, totalW - left);
                  if (w < 4) return null;
                  const live = nowMs >= cellStartMs && nowMs < cellEndMs;
                  return (
                    <div key={i} className={`hc-guide-cell${live ? " is-live" : ""}`}
                         style={{ left, width: w, "--accent": ch.accent }}>
                      <span className="hc-guide-cell-time">{window.hcFormat12h(slot.time)}</span>
                      <span className="hc-guide-cell-artist">{slot.artist}</span>
                    </div>
                  );
                })}
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function LineupScreen({ channels, now, onPick, theme, themeMode, onGuide }) {
  const resolved = useMemo(() => channels.map((c) => ({ c, r: window.hcResolveSlot(c, now) })), [channels, now]);
  const liveItems = resolved.filter(({ r }) => r.state === "live").map(({ c, r }) => `CH ${c.no} • ${c.name} → ${r.slot.artist}`);
  const marqueeItems = liveItems.length ? liveItems : [`STAGES POWER UP @ 2:00 PM PST`, `WELCOME TO HOMECHELLA`];

  return (
    <div className="hc-lineup">
      <PosterBackdrop theme={theme} />
      <header className="hc-lineup-hd">
        <HomechellaLogo />
        <div className="hc-lineup-clock">
          <div className="hc-clock-day">{now.weekday || "SAT"} • MAY 12</div>
          <div className="hc-clock-time">{window.hcFormatClock(now)}</div>
          {themeMode === "auto" && (
            <div className="hc-clock-theme">▼ AUTO • {THEME_LABEL[theme]} ▼</div>
          )}
          {now.simulated && <div className="hc-clock-sim">⚡ SIMULATED CLOCK</div>}
        </div>
        <button className="hc-pix-btn hc-guide-btn" onClick={onGuide}>≡ LINEUP</button>
      </header>
      <Marquee items={marqueeItems} speed={Math.max(20, marqueeItems.length * 8)} />
      <div className="hc-list">
        {resolved.map(({ c, r }) => (
          <ChannelRow key={c.id} channel={c} resolved={r} onClick={() => onPick(c)} />
        ))}
      </div>
      <footer className="hc-lineup-ft">
        <span>▲ PRESS A CHANNEL TO TUNE IN ▲</span>
        <span>HOMECHELLA BROADCAST SYSTEM • EST. 1996</span>
      </footer>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Between-sets countdown

function CountdownCard({ channel, secsUntil, nextSlot }) {
  return (
    <div className="hc-countdown" style={{ "--accent": channel.accent }}>
      <div className="hc-countdown-stripe" />
      <div className="hc-countdown-glyph"><ChannelGlyph kind={channel.glyph} scale={6} /></div>
      <div className="hc-countdown-meta">
        <div className="hc-countdown-up">▼ UP NEXT ON CH {String(channel.no).padStart(2, "0")} ▼</div>
        <div className="hc-countdown-artist">{nextSlot.artist}</div>
        <div className="hc-countdown-stage">{channel.stage} • {window.hcFormat12h(nextSlot.time)} PST</div>
      </div>
      <div className="hc-countdown-timer">
        <div className="hc-countdown-label">STARTS IN</div>
        <div className="hc-countdown-clock">{window.hcFormatCountdown(secsUntil)}</div>
      </div>
      <div className="hc-countdown-tip">PLEASE STAND BY • SOUND CHECK IN PROGRESS</div>
    </div>
  );
}

function SignedOffCard({ channel, nextSlot, secsUntil }) {
  return (
    <div className="hc-signedoff">
      <div className="hc-signedoff-bars">
        <span style={{ background: "#fff" }} />
        <span style={{ background: "#ffd23f" }} />
        <span style={{ background: "#7be0ff" }} />
        <span style={{ background: "#5cffd3" }} />
        <span style={{ background: "#ff5cf2" }} />
        <span style={{ background: "#a48cff" }} />
        <span style={{ background: "#0d0420" }} />
      </div>
      <div className="hc-signedoff-text">
        <div className="hc-signedoff-1">CH {String(channel.no).padStart(2, "0")} SIGNED OFF</div>
        <div className="hc-signedoff-2">{channel.name} • {channel.stage}</div>
        {nextSlot && (
          <div className="hc-signedoff-3">RETURNS WITH {nextSlot.artist} • {window.hcFormat12h(nextSlot.time)} PST</div>
        )}
        <div className="hc-signedoff-4">▮ NO SIGNAL ▮</div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Player screen — CRT + iframe + chat + reactions

function YouTubePlayer({ slot, offsetSec, muted }) {
  // Freeze offsetSec on mount — we don't want src to change every second.
  const frozenOffset = useRef(Math.max(0, Math.floor(offsetSec)));
  const frozenMuted  = useRef(muted);
  const src = `https://www.youtube-nocookie.com/embed/${slot.videoId}` +
    `?autoplay=1&start=${frozenOffset.current}` +
    `&mute=${frozenMuted.current ? 1 : 0}&playsinline=1&modestbranding=1&rel=0&controls=1&vq=hd1080`;
  return (
    <iframe
      className="hc-yt"
      src={src}
      title={slot.artist}
      allow="autoplay; encrypted-media; picture-in-picture"
      allowFullScreen
      frameBorder="0"
    />
  );
}

function PlayerScreen({ channel, now, onBack, onPip, onChannelUp, onChannelDown, tweaks }) {
  const resolved = useMemo(() => window.hcResolveSlot(channel, now), [channel, now]);
  const [bursts, burst] = useReactions();

  const fire = useCallback((emoji, color) => {
    burst(emoji, color);
    // Trigger 2-3 in a quick succession for satisfaction
    setTimeout(() => burst(emoji, color), 120);
    setTimeout(() => burst(emoji, color), 280);
  }, [burst]);

  // Hotkeys
  useEffect(() => {
    const h = (e) => {
      if (e.key === "Escape") onBack();
      if (e.key === "1") fire("🔥", "#ff8a3d");
      if (e.key === "2") fire("❤️", "#ff5cf2");
      if (e.key === "3") fire("🤘", "#ffd23f");
    };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [onBack, fire]);

  let body;
  if (resolved.state === "live") {
    body = <YouTubePlayer key={resolved.slot.videoId} slot={resolved.slot} offsetSec={resolved.offsetSec} muted={tweaks.muted} />;
  } else if (resolved.state === "soon") {
    body = <CountdownCard channel={channel} secsUntil={resolved.secsUntil} nextSlot={resolved.nextSlot} />;
  } else {
    body = <SignedOffCard channel={channel} nextSlot={resolved.nextSlot} secsUntil={resolved.secsUntil} />;
  }

  return (
    <div className="hc-player" style={{ "--accent": channel.accent }}>
      <header className="hc-player-hd">
        <button className="hc-pix-btn" onClick={onBack}>◀ LINEUP</button>
        <button className="hc-pix-btn hc-ch-btn" onClick={onChannelDown} title="Channel down" aria-label="Channel down">▼ CHANNEL</button>
        <button className="hc-pix-btn hc-ch-btn" onClick={onChannelUp}   title="Channel up"   aria-label="Channel up">▲ CHANNEL</button>
        <div className="hc-player-meta">
          <div className="hc-player-channel">CH {String(channel.no).padStart(2, "0")} • {channel.name}</div>
          <div className="hc-player-stage">{channel.stage}</div>
        </div>
        <div className="hc-player-clock">
          <div className="hc-clock-time hc-clock-time--sm">{window.hcFormatClock(now)}</div>
          {now.simulated && <div className="hc-clock-sim hc-clock-sim--sm">⚡ SIM</div>}
        </div>
        <button className="hc-pix-btn" onClick={onPip}>PiP ▣</button>
      </header>

      <main className="hc-player-main">
        <div className="hc-player-left">
          <CRTBezel channel={channel} channelNo={channel.no}>
            {body}
          </CRTBezel>
          <ReactionLayer bursts={bursts} />

          <div className="hc-now-strip">
            <div className="hc-now-strip-l">
              {resolved.state === "live" && resolved.slot && (
                <>
                  <span className="hc-now-pill">● LIVE</span>
                  <span className="hc-now-artist">{resolved.slot.artist}</span>
                  <span className="hc-now-dur">
                    {window.hcFormatCountdown(resolved.offsetSec)} / {resolved.slot.duration}:00
                  </span>
                </>
              )}
              {resolved.state === "soon" && resolved.nextSlot && (
                <>
                  <span className="hc-now-pill hc-now-pill--soon">▶ NEXT</span>
                  <span className="hc-now-artist">{resolved.nextSlot.artist}</span>
                  <span className="hc-now-dur">@ {window.hcFormat12h(resolved.nextSlot.time)} PST</span>
                </>
              )}
              {resolved.state === "dark" && (
                <>
                  <span className="hc-now-pill hc-now-pill--dark">▮ OFF AIR</span>
                  <span className="hc-now-artist">RETURNS TOMORROW</span>
                </>
              )}
            </div>
            <div className="hc-now-strip-r">
              <button className="hc-react-btn" onClick={() => fire("🔥", "#ff8a3d")}><span>🔥</span>1</button>
              <button className="hc-react-btn" onClick={() => fire("❤️", "#ff5cf2")}><span>❤️</span>2</button>
              <button className="hc-react-btn" onClick={() => fire("🤘", "#ffd23f")}><span>🤘</span>3</button>
            </div>
          </div>

          <UpNextStrip channel={channel} now={now} />
        </div>

        {tweaks.showChat && (
          <ChatPanel channel={channel} slot={resolved.slot} state={resolved.state} density={tweaks.chatDensity} />
        )}
      </main>
    </div>
  );
}

function UpNextStrip({ channel, now }) {
  const nowMs = Date.now();
  const nowSec = now.wallSec;
  const slots = channel.lineup.map((s) => {
    const startMs = s.startMs || (nowMs - nowSec * 1000 + window.hcSlotSec(s.time) * 1000);
    const endMs   = startMs + s.duration * 60 * 1000;
    return { ...s, startMs, endMs };
  });
  return (
    <div className="hc-upnext">
      <div className="hc-upnext-label">TODAY ON {channel.name}</div>
      <div className="hc-upnext-row">
        {slots.map((s, i) => {
          const live = nowMs >= s.startMs && nowMs < s.endMs;
          const past = nowMs >= s.endMs;
          return (
            <div key={i} className={`hc-upnext-slot ${live ? "is-live" : past ? "is-past" : ""}`}>
              <div className="hc-upnext-time">{window.hcFormat12h(s.time)}</div>
              <div className="hc-upnext-artist">{s.artist}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Picture-in-Picture mini player

function PiPPlayer({ channel, now, tweaks, onExpand, onClose }) {
  const resolved = useMemo(() => window.hcResolveSlot(channel, now), [channel, now]);
  return (
    <div className="hc-pip" style={{ "--accent": channel.accent }}>
      <div className="hc-pip-screen">
        {resolved.state === "live" ? (
          <YouTubePlayer slot={resolved.slot} offsetSec={resolved.offsetSec} muted={true} />
        ) : (
          <div className="hc-pip-static">
            <div className="hc-pip-static-bars" />
            <span>NO SIGNAL</span>
          </div>
        )}
        <div className="hc-pip-scan" />
      </div>
      <div className="hc-pip-meta">
        <div className="hc-pip-ch">CH {String(channel.no).padStart(2, "0")}</div>
        <div className="hc-pip-artist">{resolved.slot?.artist || "OFF AIR"}</div>
      </div>
      <div className="hc-pip-ctrls">
        <button onClick={onExpand} title="Expand">▣</button>
        <button onClick={onClose} title="Close">✕</button>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Root app

function App() {
  const [channels, setChannels] = useState(() => window.HOMECHELLA_CHANNELS || []);
  useEffect(() => {
    if (window.HC_READY) {
      window.HC_READY.then(chs => { if (chs && chs.length > 0) setChannels([...chs]); });
    }
  }, []);
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [intro, setIntro] = useState(true);
  const [view, setView] = useState({ name: "lineup", channelId: null });
  const [pip, setPip] = useState(null);
  const [showGuide, setShowGuide] = useState(false);

  const now = useNow(t.simulateTime, t.simHour, t.simMinute);

  const activeChannel = useMemo(() => channels.find((c) => c.id === view.channelId), [view.channelId]);

  const pickChannel = (c) => {
    // If PiP is showing this channel, dismiss it.
    if (pip && pip.id === c.id) setPip(null);
    setView({ name: "player", channelId: c.id });
  };

  const cycleChannel = (dir) => {
    if (!activeChannel || channels.length === 0) return;
    const idx = channels.findIndex((c) => c.id === activeChannel.id);
    if (idx < 0) return;
    const next = channels[(idx + dir + channels.length) % channels.length];
    if (pip && pip.id === next.id) setPip(null);
    setView({ name: "player", channelId: next.id });
  };

  const goBack = () => setView({ name: "lineup", channelId: null });

  const startPip = () => {
    if (activeChannel) setPip(activeChannel);
    goBack();
  };

  const expandPip = () => {
    if (pip) setView({ name: "player", channelId: pip.id });
  };

  // Resolve effective theme from "auto" or explicit.
  const effectiveTheme = useMemo(
    () => (t.theme === "auto" ? resolveAutoTheme(now.H) : t.theme),
    [t.theme, now.H]
  );

  // Apply theme to html element so global styles read it.
  useEffect(() => {
    const T = THEMES[effectiveTheme] || THEMES["neon-night"];
    const root = document.documentElement;
    for (const k of Object.keys(T)) root.style.setProperty(`--hc-${k}`, T[k]);
    document.body.setAttribute("data-crt", t.crtIntensity);
    document.body.setAttribute("data-scanlines", t.showScanlines ? "on" : "off");
    document.body.setAttribute("data-theme", effectiveTheme);
  }, [effectiveTheme, t.crtIntensity, t.showScanlines]);

  return (
    <div className="hc-root" data-screen-label={view.name === "player" ? `Player • CH ${activeChannel?.no}` : "Lineup"}>
      {intro && <CRTIntro onDone={() => setIntro(false)} />}

      {view.name === "lineup" && (
        <LineupScreen channels={channels} now={now} onPick={pickChannel}
          theme={effectiveTheme} themeMode={t.theme}
          onGuide={() => setShowGuide(true)} />
      )}
      {showGuide && (
        <LineupOverlay channels={channels} now={now} onClose={() => setShowGuide(false)} />
      )}
      {view.name === "player" && activeChannel && (
        <PlayerScreen
          channel={activeChannel}
          now={now}
          onBack={goBack}
          onPip={startPip}
          onChannelUp={() => cycleChannel(1)}
          onChannelDown={() => cycleChannel(-1)}
          tweaks={t}
        />
      )}

      {pip && view.name === "lineup" && (
        <PiPPlayer
          channel={pip}
          now={now}
          tweaks={t}
          onExpand={expandPip}
          onClose={() => setPip(null)}
        />
      )}

      <TweaksPanel title="Homechella Tweaks">
        <TweakSection label="Theme" />
        <TweakRadio label="Palette" value={t.theme}
          options={[
            { label: "Auto",   value: "auto" },
            { label: "Neon",   value: "neon-night" },
            { label: "Dusk",   value: "desert-dusk" },
            { label: "Day",    value: "daylight" },
          ]}
          onChange={(v) => setTweak("theme", v)} />
        <TweakRadio label="CRT" value={t.crtIntensity}
          options={[
            { label: "Soft",  value: "soft" },
            { label: "Med",   value: "medium" },
            { label: "Heavy", value: "heavy" },
          ]}
          onChange={(v) => setTweak("crtIntensity", v)} />
        <TweakToggle label="Scanlines" value={t.showScanlines} onChange={(v) => setTweak("showScanlines", v)} />

        <TweakSection label="Player" />
        <TweakToggle label="Start muted" value={t.muted} onChange={(v) => setTweak("muted", v)} />
        <TweakToggle label="Chat" value={t.showChat} onChange={(v) => setTweak("showChat", v)} />
        <TweakRadio label="Chat speed" value={t.chatDensity}
          options={[
            { label: "Quiet",  value: "quiet" },
            { label: "Normal", value: "normal" },
            { label: "Lively", value: "lively" },
          ]}
          onChange={(v) => setTweak("chatDensity", v)} />

        <TweakSection label="Clock" />
        <TweakToggle label="Simulate time" value={t.simulateTime} onChange={(v) => setTweak("simulateTime", v)} />
        {t.simulateTime && (
          <>
            <TweakSlider label="Sim hour" value={t.simHour} min={13} max={23} step={1}
              onChange={(v) => setTweak("simHour", v)} />
            <TweakSlider label="Sim minute" value={t.simMinute} min={0} max={59} step={1}
              onChange={(v) => setTweak("simMinute", v)} />
          </>
        )}
        <TweakButton label="Replay intro" onClick={() => setIntro(true)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
