// useSyncExternalStore over a low-frequency set of HTMLAudioElement events.
// Snapshot is cached; consumers only re-render when one of the tracked fields
// actually changes (Object.is on the snapshot). See ADR-001 / research 03 §2.

import { useSyncExternalStore } from 'react';
import type { PlayerErrorReason, PlayerState, PlayerStateKind } from '../types';

const TRACKED = [
  'play',
  'pause',
  'ended',
  'loadedmetadata',
  'durationchange',
  'emptied',
  'waiting',
  'canplay',
  'error',
] as const;

type Snapshot = {
  paused: boolean;
  ended: boolean;
  duration: number;
  ready: boolean;
  errorCode: number | null;
};

function readSnapshot(audio: HTMLAudioElement): Snapshot {
  return {
    paused: audio.paused,
    ended: audio.ended,
    duration: Number.isFinite(audio.duration) ? audio.duration : 0,
    ready: audio.readyState >= 2,
    errorCode: audio.error?.code ?? null,
  };
}

function snapshotsEqual(a: Snapshot, b: Snapshot): boolean {
  return (
    a.paused === b.paused &&
    a.ended === b.ended &&
    a.duration === b.duration &&
    a.ready === b.ready &&
    a.errorCode === b.errorCode
  );
}

export function createAudioSnapshotSource(audio: HTMLAudioElement) {
  let cached: Snapshot = readSnapshot(audio);
  return {
    subscribe(cb: () => void): () => void {
      const refresh = () => {
        const next = readSnapshot(audio);
        if (!snapshotsEqual(cached, next)) {
          cached = next;
          cb();
        }
      };
      for (const e of TRACKED) audio.addEventListener(e, refresh);
      return () => {
        for (const e of TRACKED) audio.removeEventListener(e, refresh);
      };
    },
    getSnapshot(): Snapshot {
      return cached;
    },
    getServerSnapshot(): Snapshot {
      return cached;
    },
  };
}

function errorCodeToReason(code: number | null): PlayerErrorReason {
  // MediaError codes: 1 ABORT, 2 NETWORK, 3 DECODE, 4 SRC_NOT_SUPPORTED.
  if (code === 2) return 'network';
  if (code === 3) return 'decode';
  if (code === 4) return 'unsupported';
  return 'unknown';
}

function snapshotToState(snap: Snapshot, hasSrc: boolean, peaks?: Float32Array): PlayerState {
  if (snap.errorCode !== null) {
    return { kind: 'error', reason: errorCodeToReason(snap.errorCode), duration: snap.duration };
  }
  if (!hasSrc) return { kind: 'idle' };
  if (!snap.ready) return { kind: 'loading' };
  const kind: PlayerStateKind = snap.ended
    ? 'ended'
    : snap.paused
      ? 'paused'
      : 'playing';
  return { kind, duration: snap.duration, peaks };
}

export function useAudioElementState(
  source: ReturnType<typeof createAudioSnapshotSource>,
  hasSrc: boolean,
  peaks?: Float32Array,
): PlayerState {
  const snap = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getServerSnapshot);
  return snapshotToState(snap, hasSrc, peaks);
}
