/* ============================================================
   ASSET REGISTRY — single source of truth for all swappable media.
   Drop a correctly-named file into one of the /assets folders and
   it appears automatically. Until the file exists, the code falls
   back to the built-in placeholder (CSS scene, synth music, etc.)
   so nothing ever breaks.

   FOLDERS
     assets/video/    looping scene backdrops + the gate-open clip
     assets/npc/      per-NPC portrait loops
     assets/enemies/  per-enemy portrait art (png with transparency)
     assets/music/    environment / shop / gate / battle tracks
     assets/icons/    optional custom glyphs (otherwise built-in SVG)
   ============================================================ */
const ASSETS = {
  // ---- the gate-open clip (plays once when the seal breaks) ----
  gateOpenVideo: 'assets/video/gate-open.mp4',

  // ---- looping scene backdrops (muted, autoplay, loop) ----
  sceneVideo: {
    'forest':      'assets/video/scene-forest.mp4',
    'deep-forest': 'assets/video/scene-deep-forest.mp4',
    'gate':        'assets/video/scene-gate.mp4',
    'warcamp':     'assets/video/scene-warcamp.mp4',
    'hall':        'assets/video/scene-hall.mp4',
    'house':       'assets/video/scene-house.mp4',
    'battle':      'assets/video/scene-battle.mp4',
  },

  // ---- NPC portrait loops (muted, autoplay, loop) ----
  npcVideo: {
    'Old Lady': 'assets/npc/old-lady.mp4',
    'Soldier':  'assets/npc/soldier.mp4',
    'Orc':      'assets/npc/orc.mp4',
    'Dwarf':    'assets/npc/dwarf.mp4',
    'Merchant': 'assets/npc/merchant.mp4',
  },

  // ---- enemy portrait art (png, transparent bg preferred) ----
  // keyed by a slug derived from the enemy name (see enemySlug)
  enemyArt: (slug)=> `assets/enemies/${slug}.png`,

  // ---- music tracks (mp3/ogg). Looping beds, crossfaded. ----
  // These play ONLY on the Main screen. Player & Moderator devices get
  // sound effects only (no music).
  music: {
    'forest':      'assets/music/forest.mp3',
    'deep-forest': 'assets/music/deep-forest.mp3',
    'village':     'assets/music/village.mp3',
    'gate':        'assets/music/gate.mp3',
    'shop':        'assets/music/shop.mp3',
    'magicshop':   'assets/music/magicshop.mp3',
    'battle':      'assets/music/battle.mp3',
  },

  // ---- the beat-puzzle sound (pitch-shifted into 4 notes in code) ----
  beatSfx: 'assets/sfx/beat.wav',

  // ---- per-shop backdrops (image OR looping video) keyed by shop type.
  // ShopView/ShopModal pick video when present, else img, else CSS fallback. ----
  // Served from Cloudflare for smooth, adaptive delivery:
  //   · videos → Cloudflare Stream HLS (.m3u8) — adaptive bitrate + poster thumb
  //   · images → Cloudflare Images — auto WebP/AVIF, resized on the edge
  // The originals live in assets/shops/ as masters; re-upload there to swap art.
  shopBg: {
    weapons:      { img: 'https://imagedelivery.net/Jn32DNbE-bACTtWk6w5t4Q/578192db-8af1-4523-dbb5-6d9da7d3e300/public' },
    armory_magic: { img: 'https://imagedelivery.net/Jn32DNbE-bACTtWk6w5t4Q/5819ed22-3349-4fa1-7867-3734c4d99c00/public' },
    provisioner:  { img: 'https://imagedelivery.net/Jn32DNbE-bACTtWk6w5t4Q/1c554fa0-d485-4090-4a62-ba3cf0b54900/public' },
    mega:         { video:  'https://customer-exwx6s621uws2brq.cloudflarestream.com/071a2bc3f33d6d6cfa421492da7f66f3/manifest/video.m3u8',
                    poster: 'https://customer-exwx6s621uws2brq.cloudflarestream.com/071a2bc3f33d6d6cfa421492da7f66f3/thumbnails/thumbnail.jpg' },
    scroll:       { video:  'https://customer-exwx6s621uws2brq.cloudflarestream.com/972651e30a5538f4890eaaadcacb3c6c/manifest/video.m3u8',
                    poster: 'https://customer-exwx6s621uws2brq.cloudflarestream.com/972651e30a5538f4890eaaadcacb3c6c/thumbnails/thumbnail.jpg' },
  },

  // ---- shopkeeper portraits (transparent PNG) keyed by shop type ----
  shopKeeper: {
    weapons:      'assets/npc/weaponsmith.png',
    armory_magic: 'assets/npc/armory-magic.png',
    provisioner:  'assets/npc/provisioner.png',
    mega:         'assets/npc/grand-bazaar.png',
    scroll:       'assets/npc/scrollkeeper.png',
  },

  // ---- action-card art: front per value (+1/+2/+5) and a shared sealed back.
  //   Values stay hidden in the deck (one neutral back); the front art is shown
  //   only when a card is revealed/played. ----
  cardBack: 'assets/cards/back-1.png',
  cardFront: { 1:'assets/cards/front-1.png', 2:'assets/cards/front-2.png', 5:'assets/cards/front-5.png' },
};
// art lookups (fall back to the +1 art for any odd value)
function cardFrontSrc(value){ const m=ASSETS.cardFront||{}; return m[value] || m[1] || ''; }
function cardBackSrc(){ return ASSETS.cardBack || ''; }
window.cardFrontSrc = cardFrontSrc;
window.cardBackSrc = cardBackSrc;

function enemySlug(name){
  return (name||'enemy').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');
}

/* ---------- media components with graceful fallback ---------- */
// A looping background video that quietly falls back to `children`
// (the CSS gradient) if the file is missing, errors, or never loads.
// `pingpong` plays the clip forward, then reverses it at 1× back to the
// start, then forward again — a seamless boomerald loop with no hard cut.
function BgVideo({ src, poster, className, onEnded, onFail, loop=true, pingpong=false, children }){
  const [failed, setFailed] = useState(false);
  const vref = useRef(null);
  const usingHlsJs = useRef(false);
  // Cloudflare Stream serves adaptive HLS (.m3u8): multiple quality renditions
  // the player auto-picks by bandwidth → instant start, no stutter.
  const isHls = !!src && /\.m3u8(\?|$)/i.test(src);

  useEffect(()=>{
    setFailed(false);
    usingHlsJs.current = false;
    const v = vref.current;
    if(!v || !src) return;
    let hls = null;
    const fail   = ()=>{ setFailed(true); if(onFail) onFail(); };
    // muted autoplay is allowed everywhere, but a freshly-mounted element fed by
    // MSE can reject play() if called a beat too early — so retry briefly.
    const tryPlay= (n=0)=>{ const p=v.play(); if(p&&p.catch) p.catch(()=>{ if(n<6) setTimeout(()=>tryPlay(n+1), 180); }); };

    if(isHls){
      // hls.js first — it works on every desktop Chromium/Firefox/Edge + Android
      // (and decodes reliably). Native canPlayType is only a trustworthy path on
      // Safari/iOS, where hls.js is unsupported; some Chromium builds report
      // "maybe" for HLS but can't actually play it, so they must NOT take native.
      if(window.Hls && window.Hls.isSupported()){
        usingHlsJs.current = true;
        hls = new window.Hls({ capLevelToPlayerSize:true, startLevel:-1, maxBufferLength:12 });
        hls.loadSource(src);
        hls.attachMedia(v);
        hls.on(window.Hls.Events.MANIFEST_PARSED, tryPlay);
        hls.on(window.Hls.Events.ERROR, (_e, data)=>{ if(data && data.fatal) fail(); });
      } else if(v.canPlayType('application/vnd.apple.mpegurl')){
        v.src = src; tryPlay();                       // Safari / iOS: native HLS
      } else { fail(); return; }
    } else {
      // progressive file (local mp4 / webm) — native looping plays smoothly.
      try{ v.src = src; v.load(); tryPlay(); }catch(_){ fail(); }
    }
    return ()=>{ if(hls) hls.destroy(); };
  }, [src]);

  if(!src || failed) return children || null;
  return (
    <video
      key={src}
      ref={vref}
      className={className}
      poster={poster}
      autoPlay muted loop={loop} playsInline preload="auto"
      onEnded={onEnded}
      onCanPlay={()=>{ const v=vref.current; if(v&&v.paused){ const p=v.play(); if(p&&p.catch) p.catch(()=>{}); } }}
      onError={()=>{ if(!usingHlsJs.current){ setFailed(true); if(onFail) onFail(); } }}
    />
  );
}

// An <img> that renders only once the file is confirmed loaded; otherwise
// shows `children` (the placeholder). Preloads off-DOM so there is never a
// broken-image flash, and times out if the server leaves the request hanging.
function ArtImg({ src, className, alt='', children }){
  const [state, setState] = useState('loading'); // loading | ok | failed
  useEffect(()=>{
    if(!src){ setState('failed'); return; }
    setState('loading');
    let done=false;
    const img = new Image();
    img.onload = ()=>{ if(!done){ done=true; setState('ok'); } };
    img.onerror = ()=>{ if(!done){ done=true; setState('failed'); } };
    img.src = src;
    const to = setTimeout(()=>{ if(!done){ done=true; setState('failed'); } }, 2500);
    return ()=>{ done=true; clearTimeout(to); img.onload=img.onerror=null; };
  }, [src]);
  if(state!=='ok') return children || null;
  return <img className={className} src={src} alt={alt}/>;
}

window.ASSETS = ASSETS;
window.enemySlug = enemySlug;
window.BgVideo = BgVideo;
window.ArtImg = ArtImg;
