// Pure preload planning — no DOM. The boot caches phase1Files() before unlocking the
// universe; the random clip pick is then restricted to eligible clips (fully loaded
// base + connecting morphs), so a transition never stalls on un-downloaded media and
// the pool grows safely as the background preload finishes. UMD so the browser gets
// `HEFPreload` and `node --test` can require() it.
//
// deps shape: { baseOf(clipId)->file, morphFile(fromId,toId)->file|null, isCached(file)->bool }
(function (root, factory) {
  const api = factory();
  if (typeof module !== "undefined" && module.exports) module.exports = api;
  else root.HEFPreload = api;
})(typeof self !== "undefined" ? self : this, function () {
  "use strict";

  // The minimal set to load before the simulator can run: the FIRST pool member of
  // every altitude + the morphs connecting adjacent first-members (both directions,
  // cyclic ring). One clip per altitude + corresponding morphs.
  function phase1Files(scales, deps) {
    const n = scales.length;
    const chosen = scales.map((s) => (s.pool && s.pool.length ? s.pool[0].clip_id : s.clip_id));
    const files = new Set();
    for (const c of chosen) {
      const bf = deps.baseOf(c);
      if (bf) files.add(bf);
    }
    for (let i = 0; i < n; i++) {
      const a = chosen[i], b = chosen[(i + 1) % n];
      for (const f of [deps.morphFile(a, b), deps.morphFile(b, a)]) if (f) files.add(f);
    }
    return [...files];
  }

  // Pool members eligible as a DESTINATION from `fromId`: base cached AND the morph
  // cached BOTH ways (so the forward transition and a turn-back are ready).
  function eligibleDestinations(poolIds, fromId, deps) {
    return poolIds.filter((c) =>
      deps.isCached(deps.baseOf(c)) &&
      deps.isCached(deps.morphFile(fromId, c)) &&
      deps.isCached(deps.morphFile(c, fromId)));
  }

  // Pool members eligible to SHOW at their own altitude (no transition): base cached.
  function eligibleMembers(poolIds, deps) {
    return poolIds.filter((c) => deps.isCached(deps.baseOf(c)));
  }

  function pick(list, rnd) {
    return list.length ? list[Math.floor(rnd() * list.length)] : null;
  }

  return { phase1Files, eligibleDestinations, eligibleMembers, pick };
});
