/* global React, Icon, Button, CatTag, CleFlag, AccredLine, Av, cx, fmtLabel, VIDEOS, VIDEO_BY_ID, CAT */
// LPE Learning Center — video player page (with CLE verification, captions,
// transcript, notes, and a live-webinar chat mode).

const { useState: useStateP, useEffect: useEffectP, useRef: useRefP, useMemo: useMemoP } = React;

function fmtClock(sec) {
  const m = Math.floor(sec / 60);
  const s = Math.floor(sec % 60);
  return `${m}:${String(s).padStart(2, "0")}`;
}
function parseChapterPct(t, total) {
  const parts = t.split(":").map(Number);
  let sec = parts.length === 3 ? parts[0] * 3600 + parts[1] * 60 + parts[2] : parts[0] * 60 + parts[1];
  return Math.min(100, (sec / total) * 100);
}
function relatedTo(v, videos = window.VIDEOS) {
  const scored = videos.filter((x) => x.id !== v.id).map((x) => {
    const shared = x.cats.filter((c) => v.cats.includes(c)).length;
    const sameSpeaker = x.speaker.name === v.speaker.name ? 1 : 0;
    return { x, score: shared * 2 + sameSpeaker + (x.progress === 0 ? 0.5 : 0) };
  });
  scored.sort((a, b) => b.score - a.score);
  return scored.slice(0, 4).map((s) => s.x);
}
function promptThresholds(durationMin) {
  const n = Math.max(1, Math.min(4, Math.round(durationMin / 15)));
  return Array.from({ length: n }, (_, i) => Math.round((i + 1) / (n + 1) * 100));
}

// ---- Attendance verification modal ----
function VerifyModal({ onConfirm, onTimeout }) {
  const [left, setLeft] = useStateP(30);
  useEffectP(() => {
    const id = setInterval(() => setLeft((l) => { if (l <= 1) { clearInterval(id); onTimeout(); return 0; } return l - 1; }), 1000);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="modal-scrim">
      <div className="modal" style={{ maxWidth: 420, borderTopColor: "var(--orange)" }}>
        <div className="m-body" style={{ textAlign: "center", padding: "32px 28px" }}>
          <div style={{ display: "flex", justifyContent: "center", marginBottom: 14 }}><Icon name="eye" size={28} style={{ color: "var(--orange)" }} /></div>
          <h3 style={{ fontSize: 20, marginBottom: 8 }}>Still watching?</h3>
          <p className="small" style={{ marginBottom: 18, maxWidth: "34ch", marginInline: "auto" }}>Confirm you're present to keep your CLE attendance record accurate. State bars require periodic verification.</p>
          <div className="verify-count" style={{ marginBottom: 18 }}>{left}s</div>
          <Button variant="primary" icon="check" onClick={onConfirm} style={{ width: "100%" }}>Yes, I'm here</Button>
        </div>
      </div>
    </div>
  );
}

// ---- Live chat panel ----
const SEED_CHAT = [
  { who: "Karen P.", host: false, time: "2m", text: "Joining from Columbus — excited for this one." },
  { who: "Daniel R.", host: false, time: "1m", text: "Quick q: does WIP financing affect the headline multiple or just the structure?" },
  { who: "Tom Lenfestey", host: true, time: "1m", text: "Great question, Daniel — it mostly affects structure. We'll cover the holdback mechanics at the 48-minute mark." },
  { who: "Priya S.", host: false, time: "just now", text: "Is the valuation worksheet shared afterward?" },
  { who: "Tom Lenfestey", host: true, time: "just now", text: "Yes — it's in the Resources tab and goes out to all registrants." },
];

function LiveChat({ learner }) {
  const [msgs, setMsgs] = useStateP(SEED_CHAT);
  const [draft, setDraft] = useStateP("");
  const bodyRef = useRefP(null);
  useEffectP(() => { if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight; }, [msgs]);
  function send(e) {
    e.preventDefault();
    if (!draft.trim()) return;
    setMsgs((m) => [...m, { who: learner.name, host: false, time: "now", text: draft.trim() }]);
    setDraft("");
  }
  return (
    <div className="chat-panel">
      <div className="chat-head">
        <span className="lt"><span className="dot" />Live Q&amp;A</span>
        <span className="ct"><Icon name="user" size={13} /> 214 attending</span>
      </div>
      <div className="chat-body" ref={bodyRef}>
        {msgs.map((m, i) => (
          <div key={i} className={cx("chat-msg", m.host && "host")}>
            <Av initials={m.who.split(" ").map((w) => w[0]).slice(0, 2).join("")} color={m.host ? "navy" : "cream"} size={28} />
            <div className="cm-body">
              <div className="cm-top">
                <span className={cx("cm-name", m.host && "host")}>{m.who}{m.host ? " · Host" : ""}</span>
                <span className="cm-time">{m.time}</span>
              </div>
              <div className="cm-text">{m.text}</div>
            </div>
          </div>
        ))}
      </div>
      <form className="chat-compose" onSubmit={send}>
        <input placeholder="Ask a question — Tom will answer in the Q&A" value={draft} onChange={(e) => setDraft(e.target.value)} />
        <button type="submit" title="Send"><Icon name="send" size={15} /></button>
      </form>
    </div>
  );
}

// ---- Notes tab ----
function NotesTab({ videoId, notes, addNote, editNote, deleteNote, curSec, onToast, onSeek }) {
  const [draft, setDraft] = useStateP("");
  const [editingId, setEditingId] = useStateP(null);
  const [editText, setEditText] = useStateP("");
  const list = notes[videoId] || [];

  function add() {
    if (!draft.trim()) return;
    addNote(videoId, { id: "note-" + Date.now(), t: fmtClock(curSec), sec: curSec, text: draft.trim() });
    setDraft("");
  }
  return (
    <div>
      <div className="note-compose">
        <textarea placeholder="Type a note…" value={draft} onChange={(e) => setDraft(e.target.value)} />
        <div className="nc-foot">
          <span className="stamp"><Icon name="clock" size={12} style={{ verticalAlign: "-2px", marginRight: 4 }} />Add note at {fmtClock(curSec)}</span>
          <Button variant="primary" size="sm" icon="plus" onClick={add}>Add note</Button>
        </div>
      </div>
      {list.length === 0 ? (
        <div className="empty"><Icon name="file" size={26} /><h4>No notes yet</h4><p>Jot a thought while you watch — it'll be stamped with the current timestamp.</p></div>
      ) : (
        <>
          <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 10 }}>
            <Button variant="ghost" size="sm" icon="download" onClick={() => onToast("Notes exported (PDF)")}>Download notes</Button>
          </div>
          <div>
            {list.sort((a, b) => a.sec - b.sec).map((n) => (
              <div key={n.id} className="note-row">
                <span className="nt-time" title="Jump to timestamp" onClick={() => onSeek && onSeek(n.sec)} style={{ cursor: "pointer", textDecoration: "underline" }}>{n.t}</span>
                <div className="nt-body">
                  {editingId === n.id ? (
                    <textarea value={editText} onChange={(e) => setEditText(e.target.value)} rows={2} />
                  ) : <div className="tx">{n.text}</div>}
                </div>
                <div className="nt-actions">
                  {editingId === n.id ? (
                    <>
                      <button title="Save" onClick={() => { editNote(videoId, n.id, editText); setEditingId(null); }}><Icon name="check" size={15} /></button>
                      <button title="Cancel" onClick={() => setEditingId(null)}><Icon name="x" size={15} /></button>
                    </>
                  ) : (
                    <>
                      <button title="Edit" onClick={() => { setEditingId(n.id); setEditText(n.text); }}><Icon name="sliders" size={15} /></button>
                      <button title="Delete" onClick={() => deleteNote(videoId, n.id)}><Icon name="x" size={15} /></button>
                    </>
                  )}
                </div>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

const SPEEDS = [0.75, 1, 1.25, 1.5, 2];

function PlayerScreen({ videoId, liveMode, tweaks, saved, onSave, onOpen, onBack, openSpeaker, progressMap, setProgress, attendedSet, onAttend, verifyMap, setVerify, attendancePrompts, notes, addNote, editNote, deleteNote, onToast, learner, videos = window.VIDEOS, videoMap = window.VIDEO_BY_ID, cleRequests = [] }) {
  const v = videoMap[videoId];

  // 404 / fallback
  if (!v) {
    return (
      <div className="lc-page">
        <button className="player-back" onClick={onBack}><Icon name="chevLeft" size={14} /> Back to library</button>
        <div className="empty"><Icon name="video" size={28} /><h4>Session not found</h4><p>This session may have been moved or is no longer available. Head back to the library to find another.</p>
          <div style={{ marginTop: 16 }}><Button variant="secondary" icon="video" onClick={onBack}>Back to library</Button></div>
        </div>
      </div>
    );
  }

  const total = v.duration * 60;
  const isLive = !!liveMode && v.format === "webinar";
  const verifyOn = v.cle.eligible && attendancePrompts && !attendedSet.has(v.id) && !isLive;

  const stored = progressMap[v.id] != null ? progressMap[v.id] : v.progress;
  const [pct, setPct] = useStateP(stored);
  const [playing, setPlaying] = useStateP(false);
  const [tab, setTab] = useStateP("overview");
  const [speed, setSpeed] = useStateP(1);
  const [volume, setVolume] = useStateP(0.8);
  const [muted, setMuted] = useStateP(false);
  const [ccOn, setCcOn] = useStateP(false);
  const [activePrompt, setActivePrompt] = useStateP(false);
  const timer = useRefP(null);
  const handledRef = useRefP([]);
  const playerRef = useRefP(null);
  const maxWatchedRef = useRefP(stored);
  const activePromptRef = useRefP(false);
  activePromptRef.current = activePrompt;
  const initialSeekDoneRef = useRefP(!v.youtubeId);
  const didInitialSeekRef = useRefP(!v.youtubeId);


  const attended = attendedSet.has(v.id);
  const recos = useMemoP(() => relatedTo(v, videos), [videoId, videos]);
  const verify = verifyMap[v.id];
  const thresholds = useMemoP(() => verifyOn ? promptThresholds(v.duration) : [], [videoId, verifyOn]);

  // init verification record
  useEffectP(() => {
    if (verifyOn && !verifyMap[v.id]) setVerify(v.id, { confirmed: 0, missed: 0, total: thresholds.length });
    handledRef.current = [];
  }, [videoId]);

  // reset on video switch
  useEffectP(() => {
    const s = progressMap[v.id] != null ? progressMap[v.id] : v.progress;
    setPct(s); setPlaying(false); setTab(isLive ? "overview" : "overview"); setActivePrompt(false);
    maxWatchedRef.current = s;
    initialSeekDoneRef.current = !v.youtubeId;
    didInitialSeekRef.current = !v.youtubeId;
    window.scrollTo({ top: 0 });
  }, [videoId]);

  // Load and initialize YouTube Iframe Player API dynamically
  useEffectP(() => {
    if (!v.youtubeId) return;

    const t = setTimeout(() => { initialSeekDoneRef.current = true; }, 3000);

    let ytPlayer = null;
    const initPlayer = () => {
      const container = document.getElementById("yt-player");
      if (!container) return;
      ytPlayer = new window.YT.Player("yt-player", {
        videoId: v.youtubeId,
        playerVars: {
          autoplay: 0,
          rel: 0,
          modestbranding: 1,
          controls: 1
        },
        events: {
          onReady: (e) => {
            e.target.setPlaybackRate(speed);
            e.target.setVolume(volume * 100);
            if (muted) e.target.mute(); else e.target.unMute();
            
            const startSecs = (pct / 100) * total;
            if (startSecs > 10 && startSecs < total - 10) {
              const mins = Math.floor(startSecs / 60);
              const secs = Math.floor(startSecs % 60);
              if (typeof onToast === "function") {
                onToast(`Resuming course playback at ${mins}:${secs < 10 ? '0' : ''}${secs}`);
              }
              e.target.seekTo(startSecs, true);
              didInitialSeekRef.current = true;
            }
            initialSeekDoneRef.current = true;
          },
          onStateChange: (e) => {
            if (e.data === window.YT.PlayerState.PLAYING) {
              if (activePromptRef.current) {
                e.target.pauseVideo();
                if (typeof onToast === "function") {
                  onToast("Confirm attendance check to resume playback");
                }
              } else {
                // Double-guarantee initial seek on first play trigger
                if (!didInitialSeekRef.current) {
                  const startSecs = (pct / 100) * total;
                  if (startSecs > 10 && startSecs < total - 10) {
                    const mins = Math.floor(startSecs / 60);
                    const secs = Math.floor(startSecs % 60);
                    if (typeof onToast === "function") {
                      onToast(`Resuming course playback at ${mins}:${secs < 10 ? '0' : ''}${secs}`);
                    }
                    e.target.seekTo(startSecs, true);
                  }
                  didInitialSeekRef.current = true;
                }

                // If they seeked forward while paused and clicked play, block it!
                if (typeof e.target.getCurrentTime === 'function') {
                  const ytTime = e.target.getCurrentTime();
                  if (ytTime !== undefined && ytTime !== null && !isNaN(ytTime)) {
                    const np = (ytTime / total) * 100;
                    if (v.cle.eligible && np > (maxWatchedRef.current || 0) + 1.5) {
                      onToast("Forward seeking is disabled for CLE compliance");
                      e.target.seekTo(((maxWatchedRef.current || 0) / 100) * total, true);
                      e.target.pauseVideo();
                      return;
                    }
                  }
                }
                setPlaying(true);
              }
            } else if (e.data === window.YT.PlayerState.PAUSED) {
              setPlaying(false);
            } else if (e.data === window.YT.PlayerState.ENDED) {
              // Block skipping to the end of the video
              if (v.cle.eligible && (maxWatchedRef.current || 0) < 90) {
                onToast("Forward seeking is disabled for CLE compliance");
                if (typeof e.target.seekTo === 'function') {
                  e.target.seekTo(((maxWatchedRef.current || 0) / 100) * total, true);
                }
                if (typeof e.target.pauseVideo === 'function') {
                  e.target.pauseVideo();
                }
                setPlaying(false);
              } else {
                setPlaying(false);
              }
            }
          }
        }
      });
      playerRef.current = ytPlayer;
    };

    if (!window.YT) {
      const tag = document.createElement("script");
      tag.src = "https://www.youtube.com/iframe_api";
      const firstScriptTag = document.getElementsByTagName("script")[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
      
      window.onYouTubeIframeAPIReady = () => {
        initPlayer();
      };
    } else if (!window.YT.Player) {
      const interval = setInterval(() => {
        if (window.YT && window.YT.Player) {
          clearInterval(interval);
          initPlayer();
        }
      }, 100);
    } else {
      initPlayer();
    }

    return () => {
      clearTimeout(t);
      if (ytPlayer && ytPlayer.destroy) {
        ytPlayer.destroy();
      }
      playerRef.current = null;
    };
  }, [videoId]);

  // Synchronize changes from React states to YouTube player
  useEffectP(() => {
    const player = playerRef.current;
    if (player && player.getPlayerState) {
      const state = player.getPlayerState();
      if (playing && state !== window.YT.PlayerState.PLAYING) {
        player.playVideo();
      } else if (!playing && state === window.YT.PlayerState.PLAYING) {
        player.pauseVideo();
      }
    }
  }, [playing]);

  useEffectP(() => {
    const player = playerRef.current;
    if (player && player.setPlaybackRate) {
      player.setPlaybackRate(speed);
    }
  }, [speed]);

  useEffectP(() => {
    const player = playerRef.current;
    if (player && player.setVolume) {
      player.setVolume(volume * 100);
    }
  }, [volume]);

  useEffectP(() => {
    const player = playerRef.current;
    if (player && player.mute) {
      if (muted) player.mute(); else player.unMute();
    }
  }, [muted]);

  const pctRef = useRefP(pct);
  pctRef.current = pct;

  // playback loop (polls YT player time for precision, falls back to custom timer simulation)
  useEffectP(() => {
    if (playing && !activePrompt) {
      timer.current = setInterval(() => {
        try {
          if (playerRef.current && typeof playerRef.current.getCurrentTime === 'function') {
            if (!initialSeekDoneRef.current) return;
            const ytTime = playerRef.current.getCurrentTime();
            if (ytTime === undefined || ytTime === null || isNaN(ytTime)) {
              return; // Skip if metadata or time is not loaded yet
            }
            
            let np = Math.min(100, (ytTime / total) * 100);
            if (isNaN(np)) return;

            // Enforce CLE compliance: Prevent skipping ahead on YouTube player timeline
            if (v.cle.eligible && np > (maxWatchedRef.current || 0) + 1.5) {
              onToast("Forward seeking is disabled for CLE compliance");
              playerRef.current.seekTo(((maxWatchedRef.current || 0) / 100) * total, true);
              np = maxWatchedRef.current || 0;
            } else {
              maxWatchedRef.current = Math.max(maxWatchedRef.current || 0, np);
            }
            
            if (verifyOn && playing) {
              for (const th of thresholds) {
                if (pctRef.current < th && np >= th && !handledRef.current.includes(th)) {
                  handledRef.current.push(th);
                  setActivePrompt(true);
                  setPlaying(false);
                  if (playerRef.current.pauseVideo) playerRef.current.pauseVideo();
                  break;
                }
              }
            }
            if (np >= 100 && playing) setPlaying(false);
            
            setPct(np);
            setProgress(v.id, np);
          } else if (playing) {
            setPct((p) => {
              const np = Math.min(100, p + (100 / total) * 2 * speed);
              maxWatchedRef.current = Math.max(maxWatchedRef.current || 0, np);
              if (verifyOn) {
                for (const th of thresholds) {
                  if (p < th && np >= th && !handledRef.current.includes(th)) {
                    handledRef.current.push(th);
                    setActivePrompt(true);
                    setPlaying(false);
                    break;
                  }
                }
              }
              if (np >= 100) setPlaying(false);
              setProgress(v.id, np);
              return np;
            });
          }
        } catch (e) {
          console.error("YT Player polling failed:", e);
        }
      }, 500);
    }
    return () => clearInterval(timer.current);
  }, [playing, activePrompt, total, speed, v.id, verifyOn, thresholds]);

  const cur = (pct / 100) * total;
  const allConfirmed = !verifyOn || (verify && verify.confirmed >= verify.total);
  const canAttend = pct >= 90 && allConfirmed;

  function confirmPrompt() {
    setVerify(v.id, { ...(verify || { confirmed: 0, missed: 0, total: thresholds.length }), confirmed: (verify ? verify.confirmed : 0) + 1 });
    setActivePrompt(false); setPlaying(true);
    if (playerRef.current && playerRef.current.playVideo) playerRef.current.playVideo();
  }
  function timeoutPrompt() {
    setVerify(v.id, { ...(verify || { confirmed: 0, missed: 0, total: thresholds.length }), missed: (verify ? verify.missed : 0) + 1 });
    setActivePrompt(false); setPlaying(true);
    if (playerRef.current && playerRef.current.playVideo) playerRef.current.playVideo();
    onToast("Verification missed — confirm the next prompt to stay on record");
  }
  function seek(e) {
    const rect = e.currentTarget.getBoundingClientRect();
    const np = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));
    
    // Enforce CLE compliance: Prevent skipping ahead on custom seek bar
    if (v.cle.eligible && np > (maxWatchedRef.current || 0) + 1.5) {
      onToast("Forward seeking is disabled for CLE compliance");
      return;
    }
    
    setPct(np);
    setProgress(v.id, np);
    const player = playerRef.current;
    if (player && player.seekTo) {
      player.seekTo((np / 100) * total, true);
    }
  }
  function cycleSpeed() { setSpeed((s) => SPEEDS[(SPEEDS.indexOf(s) + 1) % SPEEDS.length]); }

  // current caption
  const caption = useMemoP(() => {
    if (!ccOn || !v.transcript) return null;
    let cap = null;
    for (const chunk of v.transcript) {
      if (parseChapterPct(chunk.t, total) <= pct) cap = chunk.text; else break;
    }
    return cap;
  }, [ccOn, pct, v.transcript, total]);

  const attendReason = attended ? null : !allConfirmed
    ? `Confirm all ${verify ? verify.total : thresholds.length} attendance checks to enable (${verify ? verify.confirmed : 0} of ${verify ? verify.total : thresholds.length} done)`
    : pct < 90 ? `Watch at least 90% to log attendance (you're at ${Math.round(pct)}%)` : null;

  const tabs = [["overview", "Overview"], ["chapters", `Chapters · ${v.chapters.length}`]];
  if (v.transcript) tabs.push(["transcript", "Transcript"]);
  tabs.push(["resources", `Resources · ${v.resources.length}`], ["notes", `Notes${(notes[v.id] || []).length ? ` · ${(notes[v.id] || []).length}` : ""}`]);

  return (
    <div className="lc-page">
      <button className="player-back" onClick={onBack}><Icon name="chevLeft" size={14} /> Back</button>

      <div className="player-grid">
        <div>
          <div className="player-stage" style={v.youtubeId ? { padding: 0 } : {}}>
            {v.youtubeId ? (
              <div style={{ width: "100%", height: "100%" }}>
                <div id="yt-player" style={{ width: "100%", height: "100%" }} />
              </div>
            ) : (
              <>
                <span className="stage-code">{v.code} · {isLive ? "LIVE NOW" : fmtLabel(v.format)}</span>
                {!playing && pct === 0 ? <div className="stage-glyph">{v.title}</div> : null}
                {!playing ? (
                  <button className="big-play" onClick={() => setPlaying(true)} title="Play"><Icon name="playFill" size={26} /></button>
                ) : (
                  <button className="big-play" onClick={() => setPlaying(false)} title="Pause" style={{ background: "rgba(10,30,54,0.55)" }}><Icon name="pause" size={26} /></button>
                )}
              </>
            )}
            {caption ? <div className="caption-overlay" style={{ zIndex: 10 }}><span>{caption}</span></div> : null}
          </div>
          <div className="player-controls">
            <button onClick={() => setPlaying((p) => !p)} title={playing ? "Pause" : "Play"}>{playing ? <Icon name="pause" size={16} /> : <Icon name="playFill" size={16} />}</button>
            <span className="time">{fmtClock(cur)}</span>
            <div className="scrub" onClick={seek}><div className="fill" style={{ width: pct + "%" }} /><div className="knob" style={{ left: pct + "%" }} /></div>
            <span className="time">{v.durLabel}</span>
            <div className="pc-vol">
              <button onClick={() => setMuted((m) => !m)} title="Volume" style={{ background: "transparent", border: 0, cursor: "pointer", color: "inherit", display: "inline-flex", alignItems: "center" }}>
                {muted || volume === 0 ? (
                  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 5L6 9H2v6h4l5 4V5z"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>
                ) : (
                  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 5L6 9H2v6h4l5 4V5z"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
                )}
              </button>
              <input type="range" min="0" max="1" step="0.05" value={muted ? 0 : volume} onChange={(e) => { setVolume(+e.target.value); setMuted(false); }} />
            </div>
            {v.transcript ? <button className={cx("cc-btn", ccOn && "on")} onClick={() => setCcOn((c) => !c)} title="Captions"><span className="cc-badge" style={{ background: "transparent", border: "1px solid currentColor", color: "inherit" }}>CC</span></button> : null}
            <button className="pc-speed" onClick={cycleSpeed} title="Playback speed">{speed}×</button>
            <button onClick={() => {
              const el = document.querySelector('.player-stage');
              if (el && el.requestFullscreen) {
                el.requestFullscreen().catch(() => onToast('Fullscreen mode unavailable'));
              } else {
                onToast('Fullscreen mode unavailable');
              }
            }} title="Fullscreen" style={{ background: "transparent", border: 0, cursor: "pointer", color: "inherit", display: "inline-flex", alignItems: "center" }}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>
            </button>
          </div>

          {isLive ? (
            <div className="live-banner"><Icon name="info" size={16} style={{ color: "var(--orange)" }} /> {tweaks.hideCle ? "You're watching live. A recording will be available here after the session concludes." : "You're watching live. A recording and CLE certificate will be available here after the session concludes."}</div>
          ) : null}

          <div className="player-head">
            <div className="vtags" style={{ marginBottom: 12 }}>{v.cats.map((c) => <CatTag key={c} id={c} />)}</div>
            <h1>{v.title}</h1>
            <p className="ph-sub">{v.sub}</p>
            <div className="player-metarow">
              <span className="mi"><Icon name="calendar" size={14} />{v.date}</span>
              <span className="mi"><Icon name="clock" size={14} />{v.durLabel}</span>
              <span className="mi"><Icon name="eye" size={14} />{Number(v.views).toLocaleString()} views</span>
              <span className="mi"><Icon name="chart" size={14} />{v.rating} rating</span>
              {v.cle.eligible ? <CleFlag cle={v.cle} /> : null}
            </div>
            {v.cle.eligible ? <div style={{ marginTop: 12 }}><AccredLine cle={v.cle} /></div> : null}
          </div>

          <div className="lc-tabs">
            {tabs.map(([id, lab]) => (
              <button key={id} className={cx("lc-tab", tab === id && "active")} onClick={() => setTab(id)}>{lab}</button>
            ))}
          </div>

          {tab === "overview" ? (
            <div>
              <div className="prose">
                <p>{v.blurb}</p>
                <p>This {fmtLabel(v.format).toLowerCase()} is part of the LPE Learning Center series for verified members. {v.cle.eligible
                  ? `It is approved for ${v.cle.credits.toFixed(1)} hour${v.cle.credits === 1 ? "" : "s"} of ${v.cle.type.toLowerCase()} CLE credit — mark it attended to log your hours.`
                  : "It is informational and does not carry CLE credit."}</p>
              </div>
              <div style={{ marginTop: 24, display: "flex", alignItems: "center", gap: 14, padding: "16px 18px", border: "1px solid var(--grey-200)", background: "var(--white)" }}>
                <Av initials={v.speaker.initials} color={v.speaker.color} size={48} />
                <div>
                  <div className="name-link" style={{ fontWeight: 700, fontSize: 15 }} onClick={() => openSpeaker(v.speaker.name)}>{v.speaker.name}</div>
                  <div className="small" style={{ marginTop: 2 }}>{v.speaker.role}</div>
                </div>
                <Button variant="ghost" size="sm" style={{ marginLeft: "auto" }} icon="user" onClick={() => openSpeaker(v.speaker.name)}>View profile</Button>
              </div>
            </div>
          ) : null}

          {tab === "chapters" ? (
            <div className="chapters">
              {v.chapters.map((ch, i) => {
                const chPct = parseChapterPct(ch.t, total);
                const isActive = pct >= chPct && (i === v.chapters.length - 1 || pct < parseChapterPct(v.chapters[i + 1].t, total));
                return (
                  <div key={i} className={cx("chap", isActive && "active")} onClick={() => {
                    if (v.cle.eligible && chPct > (maxWatchedRef.current || 0) + 1.5) {
                      onToast("Forward seeking is disabled for CLE compliance");
                      return;
                    }
                    setPct(chPct);
                    setProgress(v.id, chPct);
                    setPlaying(true);
                    if (playerRef.current && playerRef.current.seekTo) {
                      playerRef.current.seekTo((chPct / 100) * total, true);
                    }
                  }}>
                    <span className="ct">{ch.t}</span>
                    <span className="cl">{ch.label}</span>
                    <span className="play-i"><Icon name="playFill" size={13} /></span>
                  </div>
                );
              })}
            </div>
          ) : null}

          {tab === "transcript" && v.transcript ? (
            <div className="transcript">
              {v.transcript.map((chunk, i) => {
                const chPct = parseChapterPct(chunk.t, total);
                const active = pct >= chPct && (i === v.transcript.length - 1 || pct < parseChapterPct(v.transcript[i + 1].t, total));
                return (
                  <div key={i} className={cx("tr-row", active && "active")} onClick={() => {
                    if (v.cle.eligible && chPct > (maxWatchedRef.current || 0) + 1.5) {
                      onToast("Forward seeking is disabled for CLE compliance");
                      return;
                    }
                    setPct(chPct);
                    setProgress(v.id, chPct);
                    if (playerRef.current && playerRef.current.seekTo) {
                      playerRef.current.seekTo((chPct / 100) * total, true);
                    }
                  }}>
                    <span className="tt">{chunk.t}</span>
                    <span className="tx">{chunk.text}</span>
                  </div>
                );
              })}
            </div>
          ) : null}

          {tab === "resources" ? (
            v.resources.length ? (
              <div className="res-list">
                {v.resources.map((r, i) => (
                  <div key={i} className="res-row">
                    <span className="ricon"><Icon name="file" size={18} /></span>
                    <div className="rinfo"><div className="rn">{r.name}</div><div className="rm">{r.type} · {r.size}</div></div>
                    {r.url ? (
                      <Button variant="ghost" size="sm" icon={r.url.startsWith("data:") ? "download" : "eye"} onClick={() => {
                        if (r.url.startsWith("data:")) {
                          const link = document.createElement("a");
                          link.href = r.url;
                          link.download = `${r.name}.pdf`;
                          document.body.appendChild(link);
                          link.click();
                          document.body.removeChild(link);
                          onToast(`Downloaded ${r.name}`);
                        } else {
                          window.open(r.url, "_blank");
                        }
                      }}>{r.url.startsWith("data:") ? "Download" : "Watch"}</Button>
                    ) : (
                      <Button variant="ghost" size="sm" icon="download" onClick={() => onToast(`Downloading “${r.name}”`)}>Download</Button>
                    )}
                  </div>
                ))}
              </div>
            ) : <div className="empty"><Icon name="folder" size={26} /><h4>No attached resources</h4><p>This session doesn't include downloadable materials.</p></div>
          ) : null}

          {tab === "notes" ? (
            <NotesTab
              videoId={v.id}
              notes={notes}
              addNote={addNote}
              editNote={editNote}
              deleteNote={deleteNote}
              curSec={cur}
              onToast={onToast}
              onSeek={(sec) => {
                const targetPct = (sec / total) * 100;
                if (v.cle.eligible && targetPct > (maxWatchedRef.current || 0) + 1.5) {
                  onToast("Forward seeking is disabled for CLE compliance");
                  return;
                }
                if (playerRef.current && playerRef.current.seekTo) {
                  playerRef.current.seekTo(sec, true);
                  onToast(`Skipped to ${fmtClock(sec)}`);
                }
              }}
            />
          ) : null}
        </div>

        {/* ---- side rail ---- */}
        <aside className="player-side">
          {isLive ? (
            <LiveChat learner={learner} />
          ) : (
            <>
              <div className="side-card">
                <div className="sc-lab">Your progress</div>
                <div className="resume-bar"><div className="fill" style={{ width: pct + "%" }} /></div>
                <div className="resume-meta">{Math.round(pct)}% watched · {fmtClock(cur)} of {v.durLabel}</div>
                <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                  <Button variant="primary" icon={playing ? "pause" : "playFill"} onClick={() => setPlaying((p) => !p)} style={{ width: "100%" }}>
                    {pct === 0 ? "Start watching" : pct >= 100 ? "Watch again" : playing ? "Pause" : "Resume"}
                  </Button>
                  <Button variant={saved.has(v.id) ? "navy" : "secondary"} icon={saved.has(v.id) ? "bookmarkFill" : "bookmark"} onClick={() => onSave(v.id)} style={{ width: "100%" }}>
                    {saved.has(v.id) ? "Saved to list" : "Save for later"}
                  </Button>
                </div>
              </div>

              {!tweaks.hideCle && v.cle.eligible ? (() => {
                const userEmail = learner ? (learner.email || "").toLowerCase() : "";
                const req = (cleRequests || []).find(r => r.videoId === v.id && (!userEmail || !r.learnerEmail || r.learnerEmail.toLowerCase() === userEmail));
                const isApproved = (req && req.status === "approved") || attendedSet.has(v.id);
                const isPending = req && req.status === "pending";
                const isRejected = req && req.status === "rejected";
                return (
                  <div className={cx("cle-panel", isApproved && "attended", isPending && "pending", isRejected && "rejected")}>
                    <div className="cle-top">
                      <span className="ico"><Icon name={isApproved ? "checkCircle" : isPending ? "clock" : isRejected ? "xCircle" : "award"} size={18} /></span>
                      <div><div className="ct-lab">{v.cle.type} CLE credit</div><div className="ct-val">{v.cle.credits.toFixed(1)} hour{v.cle.credits === 1 ? "" : "s"}</div></div>
                    </div>
                    {isApproved ? (
                      <p className="cle-note">Logged to your CLE record. Download your certificate from the CLE Center any time.</p>
                    ) : isPending ? (
                      <p className="cle-note" style={{ color: "#FFF3CD" }}>Affidavit submitted and pending administrator review.</p>
                    ) : isRejected ? (
                      <p className="cle-note" style={{ color: "#F8D7DA" }}>Your previous affidavit was declined. Please verify your details or watch again to re-submit.</p>
                    ) : verifyOn ? (
                      <p className="cle-note">Attendance checks: <b style={{ color: "#fff" }}>{verify ? verify.confirmed : 0} of {verify ? verify.total : thresholds.length} confirmed</b>{verify && verify.missed ? ` · ${verify.missed} missed` : ""}. Prompts appear while you watch.</p>
                    ) : (
                      <p className="cle-note">Watch the full session, then mark it attended to submit your affidavit.</p>
                    )}
                    {isApproved ? (
                      <div style={{ marginTop: 12 }}><span style={{ color: "var(--teal)", fontWeight: 700, fontSize: 13 }}><Icon name="check" size={13} style={{ marginRight: 4 }} />CLE Credits Logged</span></div>
                    ) : isPending ? (
                      <div style={{ marginTop: 12 }}>
                        <Button variant="secondary" disabled style={{ width: "100%", opacity: 0.8 }}>
                          Review Pending
                        </Button>
                      </div>
                    ) : (
                      <Button variant="primary" icon="check" onClick={() => onAttend(v.id)} disabled={!canAttend} style={{ width: "100%" }}>
                        {isRejected ? "Resubmit CLE Affidavit" : "Submit CLE Affidavit"}
                      </Button>
                    )}
                    {attendReason && !isApproved && !isPending && !isRejected ? (
                      <div style={{ display: "flex", gap: 7, marginTop: 10, fontSize: 11.5, color: "rgba(255,255,255,0.7)", alignItems: "flex-start" }}>
                        <Icon name="info" size={13} style={{ flexShrink: 0, marginTop: 1 }} />{attendReason}
                      </div>
                    ) : null}
                  </div>
                );
              })() : null}

              <div className="side-card">
                <div className="sc-lab" style={{ display: "flex", alignItems: "center", gap: 7 }}><Icon name="sparkle" size={13} style={{ color: "var(--orange)" }} /> Recommended next</div>
                <div className="upnext">
                  {recos.map((r) => (
                    <div key={r.id} className="upnext-row" onClick={() => onOpen(r.id)}>
                      <div className="un-thumb"><Icon name="playFill" size={14} /></div>
                      <div className="un-info"><div className="un-t">{r.title}</div><div className="un-m">{r.durLabel}{r.cle.eligible ? ` · ${r.cle.credits.toFixed(1)} CLE` : ""}</div></div>
                    </div>
                  ))}
                </div>
              </div>
            </>
          )}
        </aside>
      </div>

      {activePrompt ? <VerifyModal onConfirm={confirmPrompt} onTimeout={timeoutPrompt} /> : null}
    </div>
  );
}

Object.assign(window, { PlayerScreen });
