/* videos.jsx — Selected Videos: persistent, paste-to-add (single OR bulk).
   Auto-pulls title (noembed) + view count (Return YouTube Dislike API) +
   thumbnail (i.ytimg) from each link. Stored in localStorage. Exports Work. */
const { useState: useStateV, useEffect: useEffectV, useRef: useRefV } = React;

const LSV = "roan_videos_v3";

/* Published lineup — Roan's real videos. Channel, title and view count are
   baked in so they always display instantly (no reliance on flaky live
   fetches). View counts are a snapshot — re-run fetch_meta to refresh. */
const SEED_DATA = [
  { ytId: "8ppuULwBql8", channel: "StoneMountain64", title: "The ARC Raiders Community Is Fractured...", views: "112574" },
  { ytId: "th0Q8X2YGHg", channel: "StoneMountain64", title: "It's A Miracle That This Game Exists...", views: "96509" },
  { ytId: "NxL6aco_G5o", channel: "StoneMountain64", title: "PUBG Launched It's Own Extraction Shooter…", views: "78691" },
  { ytId: "oxGux4UF9Kg", channel: "StoneMountain64", title: "My Honest Review of Marathon...", views: "75309" },
  { ytId: "20_tj6KXZps", channel: "StoneMountain64", title: "Honest Thoughts On ARC Raiders 6 Months Later...", views: "68085" },
  { ytId: "SjZToNx2LEc", channel: "StoneMountain64", title: "It Looks Like Call Of Duty Learnt Its Lesson…", views: "55321" },
  { ytId: "bssHP9pVVYo", channel: "StoneMountain64", title: "The ULTIMATE ARC Raiders GUIDE For NEW PLAYERS!", views: "345667" },
  { ytId: "eRnkUx02HDE", channel: "StoneMountain64", title: "My One Week Review Of Battlefield 6...", views: "281632" },
  { ytId: "VZhcgKPkIp4", channel: "StoneMountain64", title: "Battlefield 6 With The #1 Jet Pilot In The Game...", views: "172122" },
  { ytId: "6IdMrKtMoVM", channel: "StoneMountain64", title: "Battlefield 6 With The #1 Pilot In The Game...", views: "180164" },
  { ytId: "40Vv3j-rQqI", channel: "StoneMountain64", title: "Battlefield 6 Tips To Help You INSTANTLY IMPROVE...", views: "654798" },
  { ytId: "tzAILk5CseM", channel: "StoneMountain64", title: "Call Of Duty Has Given Up On Warzone...", views: "78710" },
  { ytId: "IKUwLsgIzyc", channel: "StoneMountain64", title: "My Honest Thoughts On Delta Force After 1000 Hours...", views: "258290" },
  { ytId: "mlakrTPZJXo", channel: "StoneMountain64", title: "I Played With The #1 Pilot in Battlefield 6...", views: "418413" },
  { ytId: "li6OMdkiVCc", channel: "StoneMountain64", title: "Our Concerns About Battlefield 6 Were Addressed...", views: "103626" },
  { ytId: "XaQNlistXFw", channel: "StoneMountain64", title: "The Entire History Of Battlefield...", views: "108493" },
  { ytId: "ISbKFmpAg6M", channel: "StoneMountain64", title: "We Need To Have A Chat About Warzone...", views: "144998" },
  { ytId: "rwJRZ8U09wY", channel: "StoneMountain64", title: "Jet Gameplay In Battlefield 6 Feels So Good...", views: "769967" },
  { ytId: "EMmDZ1VSS_A", channel: "StoneMountain64", title: "The Recon Experience On Battlefield 6... (Sniper Only Gameplay)", views: "311247" },
  { ytId: "XlmB9rLpQ6A", channel: "StoneMountain64", title: "The Tarkov Community Is Completely Divided...", views: "94989" },
  { ytId: "jMrTUagr0EY", channel: "StoneMountain64", title: "Delta Force Just Had Its Biggest Update Yet...", views: "202399" },
  { ytId: "kHoD4SHtSSg", channel: "StoneMountain64", title: "So I Decided To Play PUBG in 2025...", views: "435857" },
  { ytId: "_98pVTLbkL8", channel: "StoneMountain64", title: "ALWAYS TRUST The COMMANDING OFFICER - Yolo On The Warzone", views: "170535" },
  { ytId: "Zp6t4GygMgw", channel: "StoneMountain64", title: "I'm Worried About The State Of Warzone..", views: "76230" },
  { ytId: "ZoA667da9oQ", channel: "StoneMountain64", title: "The Rise And Fall Of Bungie...", views: "44319" },
  { ytId: "-bUbyJ3zq4U", channel: "StoneMountain64", title: "The Hype Around Battlefront 2 Is Deserved...", views: "155899" },
  { ytId: "fUJtvtGW6iQ", channel: "StoneMountain64", title: "Gray Zone Warfare Is Unrecognizable Now...", views: "194958" },
  { ytId: "mSkekk8kr7I", channel: "StoneMountain64", title: "Exploring Battle Royales On The Verge Of Death...", views: "144144" },
  { ytId: "kkh-3zLh4Ok", channel: "StoneMountain64", title: "Is Verdansk Doomed To Fail?", views: "104898" },
  { ytId: "YJkLm4CTpRk", channel: "Slite", title: "i played on NINTENDO SWITCH...", views: "1364984" },
  { ytId: "P-63yUZmvwk", channel: "JCC", title: "We Gave Behzinga a Pro Footballer Test...", views: "215826" },
  { ytId: "_vHJs5_TppM", channel: "JCC", title: "How Good is ChrisMD at Football?", views: "375809" },
  { ytId: "P0lKC09tG-Y", channel: "BennyCentral", title: "Warzone 27 HUGE TIPS To INSTANTLY Get BETTER! (Verdansk Warzone)", views: "216004" },
  { ytId: "GvKyoxLcX-Y", channel: "BennyCentral", title: "23 tips to get INSTANTLY better at Modern Warfare 3 in 1 hour!", views: "249104" },
  { ytId: "UfCfWLU2D5c", channel: "BennyCentral", title: "EVERYTHING You Need To Know About Warzone IN MW3!", views: "108353" },
  { ytId: "3w-t2MqPJMQ", channel: "twomad 360", title: "Invading Random Online College Classes 5....", views: "4409993" },
  { ytId: "tT5ozaeN60I", channel: "twomad 360", title: "YLYL but If i LAUGH any viewers videos they get $10,000", views: "2414359" },
  { ytId: "y1IjlHR6wMU", channel: "twomad 360", title: "if i LAUGH any viewers videos they get $10,000 (i LAUGHED?)", views: "2601678" },
  { ytId: "lqQNebiiTPw", channel: "twomad 360", title: "TWOMAD vs. CS:GO (144p)", views: "807951" },
  { ytId: "6jxjtaPPhS4", channel: "twomad 360", title: "NOCLIP CHEAT vs. FITZ, JSCHLATT, MINX, SWAGGERSOULS", views: "1440339" },
  { ytId: "U3R02Yvap5I", channel: "Cloakzy", title: "WE HIRED A PRO TO WIN ON WARZONE...", views: "64730" },
  { ytId: "W_U3rBMP1GM", channel: "Cloakzy", title: "THE UNC SQUAD CRASHES OUT ON WARZONE..", views: "31490" },
  { ytId: "seGpfMeY5Sw", channel: "GutzyAiden", title: "This Kid Built His DREAM Gaming Setup From $0", views: "445698" },
  { ytId: "3MZf4DcqMig", channel: "GutzyAiden", title: "I Built an INSANE Laptop Gaming Setup under $3,000", views: "264327" },
  { ytId: "rmH3xTcJRI8", channel: "Guapo Live", title: "Kyedae Meets A2Guapo In Valorant!", views: "71574" },
  { ytId: "MInXtKBEQuA", channel: "Guapo Live", title: "Pro's HATE Queuing With A2guapo", views: "60360" },
  { ytId: "FV71cehZHlY", channel: "Guapo Live", title: "Carrying Streamers in VALORANT!", views: "57220" },
  { ytId: "T0pglMGP5BM", channel: "cjaiye", title: "I built a PC that gives me 1000FPS", views: "881007" },
];

const SEED_VIDEOS = SEED_DATA.map((d, i) => ({
  id: "s" + i, channel: d.channel, url: "https://www.youtube.com/watch?v=" + d.ytId, ytId: d.ytId, title: d.title, views: d.views }));

function loadVideos() {
  try {const r = localStorage.getItem(LSV);if (r) return JSON.parse(r);} catch (e) {}
  return SEED_VIDEOS;
}
function saveVideos(v) {try {localStorage.setItem(LSV, JSON.stringify(v));} catch (e) {}}

function parseIds(text) {
  if (!text) return [];
  const ids = [];
  const re = /(?:youtu\.be\/|[?&]v=|\/shorts\/|\/embed\/|\/live\/)([\w-]{11})/g;
  let m;
  while (m = re.exec(text)) ids.push(m[1]);
  if (!ids.length) {
    text.split(/[\s,]+/).forEach((tok) => {if (/^[\w-]{11}$/.test(tok)) ids.push(tok);});
  }
  return [...new Set(ids)];
}
/* Manual thumbnail overrides — for videos whose original YouTube thumbnail
   is no longer served by i.ytimg (e.g. unavailable channels). Keyed by ytId. */
const THUMB_OVERRIDES = {
  "3w-t2MqPJMQ": "assets/thumb-college.jpg", // Invading Random Online College Classes
  "y1IjlHR6wMU": "assets/thumb-ylyl.jpg",    // if i LAUGH any viewers videos they get $10,000 (i LAUGHED?)
  "lqQNebiiTPw": "assets/thumb-csgo.jpg",    // TWOMAD vs. CS:GO (144p)
};
function thumbMax(id) {return THUMB_OVERRIDES[id] || `https://i.ytimg.com/vi/${id}/maxresdefault.jpg`;}
function thumbHq(id) {return THUMB_OVERRIDES[id] || `https://i.ytimg.com/vi/${id}/hqdefault.jpg`;}

function fmtViews(v) {
  if (v === "" || v == null) return "";
  const n = typeof v === "number" ? v : Number(String(v).replace(/[, ]/g, ""));
  if (!isNaN(n) && n > 0) {
    if (n >= 1e9) return (n / 1e9).toFixed(n >= 1e10 ? 0 : 1).replace(/\.0$/, "") + "B views";
    if (n >= 1e6) return (n / 1e6).toFixed(n >= 1e7 ? 0 : 1).replace(/\.0$/, "") + "M views";
    if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e5 ? 0 : 1).replace(/\.0$/, "") + "K views";
    return n + " views";
  }
  const s = String(v).trim();
  return /view/i.test(s) ? s : s + " views";
}

/* Pull title + channel (noembed) and view count (Return YouTube Dislike API).
   Both are key-less + CORS-friendly; failures degrade to manual entry. */
async function fetchMeta(ytId) {
  const out = { title: "", channel: "", views: "" };
  try {
    const r = await fetch("https://noembed.com/embed?dataType=json&url=https://www.youtube.com/watch?v=" + ytId);
    if (r.ok) {const d = await r.json();if (d && d.title) out.title = d.title;if (d && d.author_name) out.channel = d.author_name;}
  } catch (e) {}
  try {
    const r2 = await fetch("https://returnyoutubedislikeapi.com/votes?videoId=" + ytId);
    if (r2.ok) {const d2 = await r2.json();if (d2 && typeof d2.viewCount === "number" && d2.viewCount > 0) out.views = String(d2.viewCount);}
  } catch (e) {}
  return out;
}

/* ---------- Video card ---------- */
function VideoCard({ v, onAdd, onEdit, onRemove, clone, admin }) {
  const has = !!v.ytId;
  const views = fmtViews(v.views);
  return (
    <figure className={"vcard" + (has ? " filled" : " ghost")} aria-hidden={clone}>
      {has ?
      <img className="vthumb" src={thumbMax(v.ytId)} alt={v.title || v.channel || "Video"} loading="lazy"
      onError={(e) => {if (!e.target.dataset.fb) {e.target.dataset.fb = "1";e.target.src = thumbHq(v.ytId);}}} /> :

      <button className="vghost-btn" onClick={clone ? undefined : onAdd} tabIndex={clone ? -1 : 0}>
          <span className="vghost-ic" aria-hidden="true">＋</span>
          <span className="vghost-tx">Add a video</span>
        </button>
      }
      {has && <div className="vcard-grad" aria-hidden="true"></div>}
      {has &&
      <a className="vcard-link" href={`https://www.youtube.com/watch?v=${v.ytId}`}
      target="_blank" rel="noopener noreferrer"
      aria-hidden={clone ? true : undefined} tabIndex={clone ? -1 : 0}
      aria-label={`Watch ${v.title || v.channel || "this video"} on YouTube`}>
          <span className="vplay" aria-hidden="true">▶</span>
        </a>
      }
      {has && (views || v.title || v.channel) &&
      <figcaption className="vcap">
          {views && <span className="vviews">{views}</span>}
          {(v.title || v.channel) && <span className="vtitle">{v.title || v.channel}</span>}
        </figcaption>
      }
      {has && !clone && admin &&
      <div className="vcard-tools">
          <button className="vtool" onClick={onEdit} aria-label="Edit">✎</button>
          <button className="vtool" onClick={onRemove} aria-label="Remove">✕</button>
        </div>
      }
      {v.channel && <span className={"vchan-tag" + (has ? "" : " on-ghost")}>{v.channel}</span>}
    </figure>);

}

/* ---------- Seamless sliding rail ---------- */
function VideoRail({ items, dir, handlers, admin }) {
  if (!items.length) return null;
  // Repeat the set enough to overflow the viewport, then duplicate that whole
  // run once and translate by exactly one run width → never a visible gap.
  const reps = Math.max(2, Math.ceil(8 / items.length));
  const run = [];
  for (let r = 0; r < reps; r++) run.push(...items);
  const track = [...run, ...run];
  // Speed scales with the number of cards so it reads at a calm, steady pace.
  const dur = Math.round(run.length * (dir === "right" ? 5.6 : 5.2));
  return (
    <div className="vrail">
      <div className="ticker" style={{ "--dur": dur + "s" }}>
        <div className={"ticker-track" + (dir === "right" ? " rev" : "")}>
          {track.map((v, i) =>
          <VideoCard key={v.id + "-" + i} v={v} clone={i >= items.length} admin={admin}
          onAdd={() => handlers.add(v.id)} onEdit={() => handlers.edit(v.id)} onRemove={() => handlers.remove(v.id)} />
          )}
        </div>
      </div>
    </div>);

}

/* ---------- Add / edit modal ---------- */
function VideoModal({ open, initial, onSave, onClose }) {
  const [text, setText] = useStateV("");
  const [title, setTitle] = useStateV("");
  const [views, setViews] = useStateV("");
  const [channel, setChannel] = useStateV("");
  const [fetching, setFetching] = useStateV(false);

  useEffectV(() => {
    if (open) {
      setText(initial.url || "");
      setTitle(initial.title || "");
      setViews(initial.views || "");
      setChannel(initial.channel || "");
      setFetching(false);
    }
  }, [open, initial]);

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

  const ids = parseIds(text);
  const single = ids.length === 1 ? ids[0] : "";
  const multi = ids.length > 1;

  // Auto-fill title + views for a single pasted link.
  useEffectV(() => {
    if (!open || !single) return;
    let cancelled = false;
    setFetching(true);
    fetchMeta(single).then((meta) => {
      if (cancelled) return;
      setFetching(false);
      setTitle((t) => t || meta.title);
      setViews((v) => v || meta.views);
      setChannel((c) => c || meta.channel);
    });
    return () => {cancelled = true;};
  }, [single, open]);

  const canSave = ids.length > 0;
  const submit = () => {
    if (multi) onSave({ mode: "many", ids });else
    onSave({ mode: "one", data: { url: text.trim(), ytId: ids[0] || "", title: title.trim(), views: views.trim(), channel: channel.trim() } });
  };

  return (
    <div className={"addv-overlay" + (open ? " open" : "")} role="dialog" aria-modal="true" aria-label="Add videos">
      <div className="addv-scrim" onClick={onClose}></div>
      <div className="addv-panel">
        <div className="addv-head">
          <h3 className="h3">{initial.ytId ? "Edit video" : "Add videos"}</h3>
          <button className="bk-close" onClick={onClose} aria-label="Close">✕</button>
        </div>
        <div className="addv-body">
          <label className="field full">
            <span className="field-label">YouTube link{initial.ytId ? "" : "s"}</span>
            <textarea className="inp txt addv-links" value={text} onChange={(e) => setText(e.target.value)} rows={multi ? 4 : 2}
            placeholder={"Paste one link — or several, one per line, to add them all at once."} autoFocus></textarea>
            <span className="field-hint-soft">
              {ids.length === 0 && "Paste any YouTube link(s)."}
              {single && (fetching ? "Pulling title + views…" : "Title, views and thumbnail pulled automatically.")}
              {multi && `${ids.length} videos detected — titles & views pull in automatically.`}
            </span>
          </label>

          {!multi &&
          <>
              <div className="addv-preview">
                {single ?
              <img className="addv-thumb" src={thumbMax(single)} alt="preview"
              onError={(e) => {if (!e.target.dataset.fb) {e.target.dataset.fb = "1";e.target.src = thumbHq(single);}}} /> :

              <div className="addv-thumb addv-thumb--empty"><span>Thumbnail preview</span></div>
              }
              </div>
              <div className="form-grid">
                <label className="field full">
                  <span className="field-label">Title</span>
                  <input className="inp" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Pulled from the link" />
                </label>
                <label className="field">
                  <span className="field-label">Views</span>
                  <input className="inp" value={views} onChange={(e) => setViews(e.target.value)} placeholder="Auto · or type e.g. 2.4M" />
                </label>
                <label className="field">
                  <span className="field-label">Channel</span>
                  <input className="inp" value={channel} onChange={(e) => setChannel(e.target.value)} placeholder="Channel name" />
                </label>
              </div>
            </>
          }

          {multi &&
          <div className="addv-bulk">
              {ids.slice(0, 8).map((id) =>
            <div className="bulk-row" key={id}>
                  <img className="bulk-thumb" src={thumbHq(id)} alt="" />
                  <span className="bulk-id faint">youtu.be/{id}</span>
                </div>
            )}
              {ids.length > 8 && <p className="faint" style={{ fontSize: "13px" }}>+ {ids.length - 8} more</p>}
            </div>
          }
        </div>
        <div className="addv-foot">
          <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={!canSave} onClick={submit}>
            {initial.ytId ? "Save changes" : multi ? `Add ${ids.length} videos` : "Add video"} <span className="arrow">↗</span>
          </button>
        </div>
      </div>
    </div>);

}

/* Admin (owner) mode — only the owner sees Add/edit/remove controls.
   Enable by visiting the site with #edit in the URL (persists); turn it off
   with #viewer. Viewers only ever see the published videos. */
function isAdminMode() {
  try {
    const hash = (location.hash || "").toLowerCase();
    const qs = new URLSearchParams(location.search || "");
    if (hash.indexOf("edit") !== -1 || qs.has("edit") || qs.get("admin") === "1") localStorage.setItem("roan_admin", "1");
    if (hash.indexOf("viewer") !== -1 || qs.get("admin") === "off") localStorage.removeItem("roan_admin");
    return localStorage.getItem("roan_admin") === "1";
  } catch (e) {return false;}
}

/* ---------- Work section ---------- */
function Work() {
  const [videos, setVideos] = useStateV(loadVideos);
  const [modal, setModal] = useStateV(null);
  const [admin] = useStateV(isAdminMode);
  useEffectV(() => {saveVideos(videos);}, [videos]);

  // Auto-pull title + views + channel for any video missing them, on load.
  // Throttled to a few concurrent requests so YouTube's metadata endpoints
  // aren't hammered by the full lineup at once.
  const enrichedRef = useRefV(false);
  useEffectV(() => {
    if (enrichedRef.current) return;
    enrichedRef.current = true;
    const todo = videos.filter((v) => v.ytId && (!v.title || !v.views || !v.channel));
    if (!todo.length) return;
    let i = 0;
    const worker = async () => {
      while (i < todo.length) {
        const item = todo[i++];
        const meta = await fetchMeta(item.ytId);
        setVideos((prev) => prev.map((v) => v.id === item.id ?
        { ...v, title: v.title || meta.title, views: v.views || meta.views, channel: v.channel || meta.channel } :
        v));
      }
    };
    for (let c = 0; c < 4; c++) worker();
  }, []);

  const current = modal ? videos.find((v) => v.id === modal.id) || { channel: "" } : { channel: "" };

  const enrich = (entryId, ytId) => {
    fetchMeta(ytId).then((meta) => {
      setVideos((prev) => prev.map((v) => v.id === entryId ?
      { ...v, title: v.title || meta.title, views: v.views || meta.views, channel: v.channel || meta.channel } :
      v));
    });
  };

  const openAdd = (id) => setModal({ id });
  const openNew = () => setModal({ id: null });

  const save = (payload) => {
    if (payload.mode === "many") {
      const base = payload.ids.map((yt, k) => ({
        id: "v" + Date.now() + "_" + k, channel: current.channel || "", url: "https://youtu.be/" + yt, ytId: yt, title: "", views: ""
      }));
      setVideos((prev) => {
        let next = [...prev];
        // If launched from a ghost, fill that ghost with the first video.
        if (modal && modal.id) {
          const gi = next.findIndex((v) => v.id === modal.id);
          if (gi >= 0 && !next[gi].ytId) {next[gi] = { ...next[gi], ...base[0] };base[0].id = next[gi].id;return [...next, ...base.slice(1)];}
        }
        return [...next, ...base];
      });
      base.forEach((b) => enrich(b.id, b.ytId));
    } else {
      const data = payload.data;
      if (modal && modal.id) {
        setVideos((prev) => prev.map((v) => v.id === modal.id ? { ...v, ...data } : v));
      } else {
        const nid = "v" + Date.now();
        setVideos((prev) => [...prev, { id: nid, ...data }]);
      }
    }
    setModal(null);
  };

  const remove = (id) => {
    setVideos((prev) => {
      const seeded = SEED_VIDEOS.some((s) => s.id === id);
      if (seeded) return prev.map((v) => v.id === id ? { ...v, url: "", ytId: "", title: "", views: "" } : v);
      return prev.filter((v) => v.id !== id);
    });
  };

  const handlers = { add: openAdd, edit: openAdd, remove };
  // Viewers only see published videos; the owner (admin) also sees the empty
  // "add" slots so the portfolio can be built out.
  const published = admin ? videos : videos.filter((v) => v.ytId);
  // Shuffle into a fresh random order on each load so there's no fixed lineup.
  // Memoized on the set of ids so the order stays stable while metadata
  // (titles / views) streams in — only a real add/remove reshuffles.
  const idsKey = published.map((v) => v.id).join(",");
  const display = React.useMemo(() => {
    const arr = published.slice();
    for (let i = arr.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [idsKey, admin]);
  const half = Math.ceil(display.length / 2);
  const row1 = display.slice(0, half);
  const row2 = display.slice(half);

  return (
    <section className="section section--tight" id="work">
      {admin &&
      <div className="wrap">
          <div className="work-head reveal">
            <button className="addv-trigger" onClick={openNew}>
              <span aria-hidden="true">＋</span> Add videos
            </button>
          </div>
        </div>
      }
      <VideoRail items={row1} dir="left" handlers={handlers} admin={admin} />
      <VideoRail items={row2} dir="right" handlers={handlers} admin={admin} />
      <VideoModal open={!!modal} initial={current} onSave={save} onClose={() => setModal(null)} />
    </section>);
}

Object.assign(window, { Work });