/**
 * GameAudioManager — менеджер аудио для проектов в плеере (GD и др.).
 *
 * Хранит один AudioContext на сессию плеера, поддерживает:
 *   - playSfx(name) — проигрывает один из 9 SFX (jump/death/orb_tap/...)
 *   - playMusic(trackId) — играет фоновую музыку (синтез fallback / mp3 если есть)
 *   - stopMusic()
 *   - muteMusic(bool)
 *
 * AudioContext создаётся лениво при первом playSfx/playMusic. Чтобы он не был
 * suspended из-за autoplay policy, плеер вызывает unlock() при первом клике
 * (см. KubikonPlayer.jsx).
 *
 * SFX и музыка — те же фабрики что в /admin-preview/gd-sfx и /admin-preview/gd-music.
 * Реальные mp3 (если положены в /public/music/gd/) играются вместо fallback-синтеза.
 */
import { SFX_CATALOG } from '../AdminPreview/gdSfx/sfxFactories';
import { TRACKS } from '../AdminPreview/gdMusic/musicCatalog';
import { SynthPlayer, trackFileExists } from '../AdminPreview/gdMusic/musicSynth';

export class GameAudioManager {
  constructor() {
    this.ctx = null;
    this.masterGain = null;
    this.sfxIndex = {};
    for (const s of SFX_CATALOG) this.sfxIndex[s.id] = s;
    this.trackIndex = {};
    for (const t of TRACKS) this.trackIndex[t.id] = t;

    this.currentTrackId = null;
    this.audioEl = null;        // <audio> для mp3 трека (если файл есть)
    this.synth = null;          // SynthPlayer fallback
    this.muted = false;
    this.fileStatusCache = new Map();
    this._pendingMusic = null;  // если playMusic вызван до unlock — отложим

    // Глобальный first-gesture listener — браузеры блокируют AudioContext
    // до первого клика. Слушаем pointerdown/keydown/touchstart на window.
    this._unlockHandler = () => {
      this.unlock().then((ok) => {
        if (ok && this._pendingMusic) {
          const tid = this._pendingMusic;
          this._pendingMusic = null;
          this.playMusic(tid);
        }
      });
      window.removeEventListener('pointerdown', this._unlockHandler);
      window.removeEventListener('keydown', this._unlockHandler);
      window.removeEventListener('touchstart', this._unlockHandler);
    };
    try {
      window.addEventListener('pointerdown', this._unlockHandler, { once: false });
      window.addEventListener('keydown', this._unlockHandler, { once: false });
      window.addEventListener('touchstart', this._unlockHandler, { once: false });
    } catch (e) { /* SSR / no window */ }
  }

  /** Создать AudioContext (или вернуть существующий). Должен вызываться внутри
   *  user-gesture цепочки (например onClick). */
  async unlock() {
    if (!this.ctx) {
      const Ctor = window.AudioContext || window.webkitAudioContext;
      if (!Ctor) return false;
      this.ctx = new Ctor();
      this.masterGain = this.ctx.createGain();
      this.masterGain.gain.value = 0.21;   // 0.7 × 0.3 (–70% по запросу юзера)
      this.masterGain.connect(this.ctx.destination);
    }
    if (this.ctx.state === 'suspended') {
      try { await this.ctx.resume(); } catch (e) { /* ignore */ }
    }
    return this.ctx.state === 'running';
  }

  /** Проиграть короткий SFX (синхронно, без задержки). */
  playSfx(name) {
    if (this.muted) return;
    const sfx = this.sfxIndex[name];
    if (!sfx || !this.ctx || !this.masterGain) return;
    try {
      sfx.play(this.ctx, this.masterGain);
    } catch (e) {
      console.warn('[GameAudio] sfx failed', name, e);
    }
  }

  /** Запустить фоновую музыку. Если уже играет тот же трек — ничего.
   *  Если AudioContext ещё не разблокирован (нет user-gesture) — откладываем
   *  до первого клика (см. _unlockHandler). */
  async playMusic(trackId) {
    if (this.currentTrackId === trackId) return;
    if (!this.ctx) {
      // AudioContext ещё не создан — ждём первого user-gesture
      this._pendingMusic = trackId;
      return;
    }
    this.stopMusic();
    const track = this.trackIndex[trackId];
    if (!track) {
      console.warn('[GameAudio] unknown track:', trackId);
      return;
    }
    this.currentTrackId = trackId;
    if (this.muted) return;

    // Проверка наличия mp3-файла (с кешем)
    let fileExists = this.fileStatusCache.get(trackId);
    if (fileExists === undefined) {
      try { fileExists = await trackFileExists(track.file); }
      catch (e) { fileExists = false; }
      this.fileStatusCache.set(trackId, fileExists);
    }

    if (fileExists) {
      // Играем настоящий файл
      try {
        const a = new Audio(track.file);
        a.loop = true;
        a.volume = 0.165;   // 0.55 × 0.3 (–70%)
        await a.play();
        this.audioEl = a;
      } catch (e) {
        console.warn('[GameAudio] audio.play() failed, fallback to synth', e);
        this._startSynth(track);
      }
    } else {
      this._startSynth(track);
    }
  }

  _startSynth(track) {
    try {
      this.synth = new SynthPlayer(track.fallbackSynth, track.bpm);
      // SynthPlayer создаёт свой AudioContext — ок, не конфликтует с нашим
      this.synth.start();
    } catch (e) {
      console.warn('[GameAudio] synth.start() failed', e);
    }
  }

  stopMusic() {
    if (this.audioEl) {
      try { this.audioEl.pause(); } catch (e) {}
      this.audioEl = null;
    }
    if (this.synth) {
      try { this.synth.stop(); } catch (e) {}
      this.synth = null;
    }
    this.currentTrackId = null;
  }

  setMuted(mute) {
    this.muted = !!mute;
    if (this.muted) {
      // временно глушим — но не стираем currentTrackId
      if (this.audioEl) {
        try { this.audioEl.volume = 0; } catch (e) {}
      }
      if (this.synth) {
        const wasId = this.currentTrackId;
        this.stopMusic();
        this.currentTrackId = wasId;
      }
    } else if (this.currentTrackId) {
      const tid = this.currentTrackId;
      this.currentTrackId = null;
      this.playMusic(tid);
    }
  }

  dispose() {
    this.stopMusic();
    try {
      window.removeEventListener('pointerdown', this._unlockHandler);
      window.removeEventListener('keydown', this._unlockHandler);
      window.removeEventListener('touchstart', this._unlockHandler);
    } catch (e) {}
    if (this.ctx) {
      try { this.ctx.close(); } catch (e) {}
      this.ctx = null;
    }
  }
}
