import mascotPoster from '../../../../public/res/img/mascot.png';
import mascotWebm from '../../../../public/res/img/mascot.webm';

/*
 * Module-level singleton <video> for the auth/splash mascot.
 *
 * AuthSplashScreen renders in three different React tree positions during
 * cold boot (ConfigConfigLoading → SpecVersionsLoader fallback →
 * ClientRootLoading), and React unmount/mount across positions destroys and
 * re-creates the underlying <video> element. autoplay then restarts from
 * frame 0 for every new instance, which the user perceives as the mascot
 * loop "skipping" mid-boot.
 *
 * Keeping one <video> on the module level and attaching/detaching it across
 * AuthMascot mounts preserves currentTime, so the animation reads as one
 * continuous loop. We also expose a readiness Promise so AuthMascot can
 * still gate the native splash hand-off on the FIRST canplay/loadeddata
 * event — subsequent attaches resolve instantly because the media is
 * already buffered.
 *
 * KNOWN ARCHITECTURAL CAVEAT: this is a module-level singleton with mutable
 * state and imperative DOM manipulation — pragmatic but non-idiomatic for
 * Concurrent React. The canonical fix is React 19.2's <Activity mode="hidden">
 * (formerly <Offscreen>), which preserves video currentTime by design. When
 * we upgrade React to 19.2+, replace this file with an <Activity>-wrapped
 * AuthMascot rendered once at App.tsx top-level, controlled by a jotai atom
 * that AuthSplashScreen sets/clears on mount. See:
 *   https://react.dev/reference/react/Activity
 * Constraints of the current implementation:
 *   - Single instance only (rendering two AuthMascot simultaneously breaks).
 *   - Not SSR-safe (createElement at module load).
 *   - HMR-fragile (the singleton survives reloads with stale closure refs).
 */

let videoEl: HTMLVideoElement | null = null;
let readyPromise: Promise<void> | null = null;

const ensureVideo = (): HTMLVideoElement => {
  if (videoEl) return videoEl;

  const v = document.createElement('video');
  v.autoplay = true;
  v.loop = true;
  v.muted = true;
  v.playsInline = true;
  v.preload = 'auto';
  v.poster = mascotPoster;
  v.setAttribute('aria-hidden', 'true');

  const source = document.createElement('source');
  source.src = mascotWebm;
  source.type = 'video/webm';
  v.appendChild(source);

  readyPromise = new Promise<void>((resolve) => {
    if (v.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
      resolve();
      return;
    }
    const onReady = () => {
      v.removeEventListener('canplay', onReady);
      v.removeEventListener('loadeddata', onReady);
      v.removeEventListener('error', onReady);
      resolve();
    };
    v.addEventListener('canplay', onReady, { once: true });
    v.addEventListener('loadeddata', onReady, { once: true });
    // Resolve on error too: if the codec is unsupported (e.g. legacy WebView
    // that doesn't decode webm) we still need to release the native splash so
    // the user sees AuthLayout / login. The poster <img> falls back as the
    // visible mascot. Without this branch readyPromise hangs and the native
    // splash sits on the safety timeout.
    v.addEventListener('error', onReady, { once: true });
  });

  videoEl = v;
  return v;
};

export const attachMascotVideo = (container: HTMLElement, className: string): void => {
  const v = ensureVideo();
  if (v.className !== className) v.className = className;
  if (v.parentElement === container) return;
  container.appendChild(v);
  // detach from previous parent is implicit (DOM moves the node), and
  // re-running play() handles the rare case where Chrome paused the media
  // while it was orphan-detached between commits.
  v.play().catch(() => undefined);
};

export const detachMascotVideo = (container: HTMLElement): void => {
  if (!videoEl) return;
  if (videoEl.parentElement === container) {
    container.removeChild(videoEl);
  }
};

// First frame ready (poster decoded OR video buffered to canplay).
// Subsequent calls return the resolved Promise instantly.
export const mascotVideoReady = (): Promise<void> => {
  ensureVideo();
  return readyPromise ?? Promise.resolve();
};
