(function () {
  class LanPlayPlayer {
    constructor(video, wrapper, onError, onLoading, onNext) {
      this.video = video;
      this.wrapper = wrapper;
      this.onError = onError;
      this.onLoading = onLoading;
      this.onNext = onNext;
      this.dashPlayer = null;
      this.controlBar = null;
      this.sessionId = null;
      this.mediaPath = null;
      this.dashPreferencesApplied = false;
      this.currentEntry = null;
      this.nextAvailable = false;
      this.restarting = false;
      this.operation = 0;
      this.freezeFrame = null;
      this.playingHandler = null;
      this.subtitleUrls = [];
    }

    async play(entry, startSeconds = 0, preserveFrame = false) {
      const framePreserved = preserveFrame && this.captureFrame();
      this.onLoading(true, framePreserved);
      this.stop({ preserveFrame: framePreserved });
      const operation = ++this.operation;
      this.currentEntry = entry;
      this.mediaPath = entry.path;
      this.dashPreferencesApplied = false;
      const session = await window.LanPlayApi.startDash(entry.path, startSeconds);
      if (operation !== this.operation) {
        window.LanPlayApi.stopDash(session.session_id).catch(() => {});
        return;
      }
      this.sessionId = session.session_id;
      this.dashPlayer = dashjs.MediaPlayer().create();
      this.dashPlayer.updateSettings({
        streaming: {
          buffer: {
            bufferTimeDefault: 8,
            bufferTimeAtTopQuality: 8,
            bufferTimeAtTopQualityLongForm: 8
          }
        }
      });
      this.dashPlayer.initialize(this.video, null, true);
      await this.addExternalSubtitles(entry.path, session.start_seconds, operation);
      if (operation !== this.operation) return;
      this.controlBar = new DashControlBarModule.ControlBar(this.dashPlayer, this.video);
      this.controlBar.init(this.wrapper);
      this.controlBar.setVodDuration(session.duration_seconds, session.start_seconds);
      if (typeof this.controlBar.onSeekRequested === 'function') {
        this.controlBar.onSeekRequested((time) => this.seek(time));
      }
      if (typeof this.controlBar.onNextRequested === 'function') {
        this.controlBar.onNextRequested(() => this.onNext());
      }
      if (typeof this.controlBar.setNextAvailable === 'function') {
        this.controlBar.setNextAvailable(this.nextAvailable);
      }
      this.controlBar.onTrackSelected((type, track) => this.rememberTrack(type, track));
      this.controlBar.disable();
      const controlBar = this.controlBar;
      this.dashPlayer.on(dashjs.MediaPlayer.events.ERROR, (event) => {
        if (operation !== this.operation) return;
        const message = event && event.error && event.error.message;
        this.onError(message || 'DASH playback error');
      });
      this.dashPlayer.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, () => {
        if (operation !== this.operation || this.controlBar !== controlBar) return;
        this.applyDashPreferences();
        controlBar.enable();
        controlBar.refreshTracks();
      });
      this.playingHandler = () => {
        if (operation !== this.operation) return;
        this.playingHandler = null;
        this.clearFreezeFrame();
        this.onLoading(false, false);
      };
      this.video.addEventListener('playing', this.playingHandler, { once: true });
      this.dashPlayer.attachSource(session.manifest_url);
      if (!framePreserved) this.onLoading(false, false);
    }

    async seek(time) {
      if (this.restarting || !this.currentEntry) return;
      this.restarting = true;
      const entry = this.currentEntry;
      try { await this.play(entry, time, true); }
      catch (error) {
        this.onError(error.message);
        this.clearFreezeFrame();
        this.onLoading(false, false);
      } finally { this.restarting = false; }
    }

    setNextAvailable(available) {
      this.nextAvailable = Boolean(available);
      if (this.controlBar && typeof this.controlBar.setNextAvailable === 'function') {
        this.controlBar.setNextAvailable(this.nextAvailable);
      }
    }

    applyDashPreferences() {
      if (this.dashPreferencesApplied || !this.mediaPath) return;
      ['audio', 'video'].forEach((type) => {
        const tracks = this.dashPlayer.getTracksFor(type) || [];
        const preferred = window.LanPlayPreferences.find(this.mediaPath, type, tracks);
        if (preferred) this.dashPlayer.setCurrentTrack(preferred);
      });
      const textTracks = this.dashPlayer.getTracksFor('text') || [];
      const preferredText = window.LanPlayPreferences.find(this.mediaPath, 'subtitle', textTracks);
      if (preferredText === null) {
        this.dashPlayer.setTextTrack(-1);
      } else if (preferredText) {
        const index = textTracks.indexOf(preferredText);
        if (index >= 0) this.dashPlayer.setTextTrack(index);
      }
      this.dashPreferencesApplied = true;
    }

    rememberTrack(type, track) {
      if (!this.mediaPath) return;
      if (type === 'nativeText') {
        const tracks = Array.from(this.video.textTracks || []);
        window.LanPlayPreferences.remember(this.mediaPath, 'subtitle', track, tracks.indexOf(track));
        return;
      }
      const preferenceType = type === 'text' ? 'subtitle' : type;
      const tracks = this.dashPlayer.getTracksFor(type) || [];
      window.LanPlayPreferences.remember(this.mediaPath, preferenceType, track, tracks.indexOf(track));
    }

    async addExternalSubtitles(path, startSeconds = 0, operation = this.operation) {
      let subtitles = [];
      try { subtitles = await window.LanPlayApi.subtitles(path); } catch (_) { return; }
      if (operation !== this.operation) return;
      const loaded = await Promise.all(subtitles.filter((item) => item.supported).map(async (subtitle, ordinal) => {
        const query = new URLSearchParams({ path, start: String(startSeconds) });
        const url = subtitle.source === 'external'
          ? `/api/subtitles/external/${encodeURIComponent(subtitle.id)}?${query}`
          : `/api/subtitles/${subtitle.index}?${query}`;
        try {
          const response = await fetch(url);
          if (!response.ok) return null;
          const contents = await response.text();
          const objectUrl = URL.createObjectURL(new Blob([contents], { type: 'text/vtt' }));
          return { subtitle, ordinal, objectUrl };
        } catch (_) {
          return null;
        }
      }));
      if (operation !== this.operation) {
        loaded.filter(Boolean).forEach((item) => URL.revokeObjectURL(item.objectUrl));
        return;
      }
      loaded.filter(Boolean).forEach(({ subtitle, ordinal, objectUrl }) => {
        const label = subtitle.title || subtitle.language || `Subtitle ${ordinal + 1}`;
        const idPart = subtitle.source === 'external' ? subtitle.id : subtitle.index;
        this.subtitleUrls.push(objectUrl);
        this.dashPlayer.addExternalSubtitle(new dashjs.ExternalSubtitle({
          id: `lan-play-${subtitle.source}-${idPart}`,
          url: objectUrl,
          language: label,
          mimeType: 'text/vtt',
          bandwidth: 256,
          periodId: '0'
        }));
      });
    }

    captureFrame() {
      if (this.video.readyState < 2 || !this.video.videoWidth || !this.video.videoHeight) return false;
      try {
        this.clearFreezeFrame();
        const canvas = document.createElement('canvas');
        const width = Math.max(1, this.wrapper.clientWidth);
        const height = Math.max(1, this.wrapper.clientHeight);
        canvas.width = Math.min(width, 1920);
        canvas.height = Math.min(height, 1080);
        canvas.className = 'player-freeze-frame';
        const context = canvas.getContext('2d');
        context.fillStyle = '#000';
        context.fillRect(0, 0, canvas.width, canvas.height);
        const scale = Math.min(canvas.width / this.video.videoWidth, canvas.height / this.video.videoHeight);
        const drawWidth = this.video.videoWidth * scale;
        const drawHeight = this.video.videoHeight * scale;
        context.drawImage(
          this.video,
          (canvas.width - drawWidth) / 2,
          (canvas.height - drawHeight) / 2,
          drawWidth,
          drawHeight
        );
        this.wrapper.appendChild(canvas);
        this.freezeFrame = canvas;
        return true;
      } catch (_) {
        this.clearFreezeFrame();
        return false;
      }
    }

    clearFreezeFrame() {
      if (this.freezeFrame) this.freezeFrame.remove();
      this.freezeFrame = null;
    }

    stop(options = {}) {
      this.operation += 1;
      if (this.playingHandler) {
        this.video.removeEventListener('playing', this.playingHandler);
        this.playingHandler = null;
      }
      if (!options.preserveFrame) this.clearFreezeFrame();
      this.revokeSubtitleUrls();
      if (this.controlBar) {
        this.controlBar.destroy();
        this.controlBar = null;
      }
      if (this.dashPlayer) {
        this.dashPlayer.destroy();
        this.dashPlayer = null;
      }
      this.video.pause();
      this.video.removeAttribute('src');
      while (this.video.firstChild) this.video.removeChild(this.video.firstChild);
      this.video.load();
      this.mediaPath = null;
      this.dashPreferencesApplied = false;
      this.currentEntry = null;
      if (this.sessionId) {
        const sessionId = this.sessionId;
        this.sessionId = null;
        window.LanPlayApi.stopDash(sessionId).catch(() => {});
      }
    }

    dispose() {
      this.operation += 1;
      if (this.playingHandler) this.video.removeEventListener('playing', this.playingHandler);
      this.playingHandler = null;
      this.clearFreezeFrame();
      this.revokeSubtitleUrls();
      if (this.controlBar) this.controlBar.destroy();
      if (this.dashPlayer) this.dashPlayer.destroy();
      if (this.sessionId) {
        fetch(`/api/dash/${this.sessionId}`, { method: 'DELETE', keepalive: true }).catch(() => {});
      }
      this.controlBar = null;
      this.dashPlayer = null;
      this.sessionId = null;
      this.currentEntry = null;
    }

    revokeSubtitleUrls() {
      this.subtitleUrls.forEach((url) => URL.revokeObjectURL(url));
      this.subtitleUrls = [];
    }
  }

  window.LanPlayPlayer = LanPlayPlayer;
})();
