/**
 * Webview audio adapter — plays audio-only media through a hidden `<audio>`
 * element on platforms with no native audio backend (currently Windows).
 *
 * All *video* already renders through the webview `<video>` element on every
 * platform; libmpv/ExoPlayer only drive audio-only playback. On Windows there is
 * no native audio backend, so the Rust `WebviewAudioBackend` hands the stream URL
 * to the frontend via a `webview_audio_load` event and drives play/pause/seek
 * through `control_command`. This adapter owns the `<audio>` element that plays
 * it and reports state/position/duration/ended back to Rust through the same
 * `player_report_*` round-trip the HTML5 video adapter uses (via {@link AdapterHost}).
 *
 * It implements the {@link PlayerAdapter} surface so it can be registered with
 * `playerController.setActiveAdapter` — but only the methods `handleControlCommand`
 * actually routes (`play`, `pause`, `seekElement`) carry audio-specific logic;
 * the video-only members (subtitles, transcode reload) are inert stubs.
 *
 * TRACES: UR-003, UR-005 | DR-004
 */

import type { AdapterHost, PlayerAdapter, PlayerLoadOptions } from "./types";

export class WebviewAudioAdapter implements PlayerAdapter {
  readonly kind = "html5" as const;

  private audio: HTMLAudioElement;
  private host: AdapterHost;
  private endedFired = false;

  constructor(audio: HTMLAudioElement, host: AdapterHost) {
    this.audio = audio;
    this.host = host;
    this.wire();
  }

  private wire(): void {
    const a = this.audio;
    a.addEventListener("loadedmetadata", () => {
      this.host.onMediaLoaded(Number.isFinite(a.duration) ? a.duration : 0);
    });
    a.addEventListener("timeupdate", () => {
      this.host.onPosition(a.currentTime, Number.isFinite(a.duration) ? a.duration : 0);
    });
    a.addEventListener("playing", () => this.host.onState("playing"));
    a.addEventListener("pause", () => {
      // A pause fired at the natural end is part of "ended"; don't report paused.
      if (!a.ended) this.host.onState("paused");
    });
    a.addEventListener("waiting", () => this.host.onBuffering(true));
    a.addEventListener("canplay", () => this.host.onReady());
    a.addEventListener("ended", () => {
      if (this.endedFired) return;
      this.endedFired = true;
      this.host.onState("stopped");
      this.host.onEnded();
    });
    a.addEventListener("error", () => {
      const err = a.error;
      this.host.onError(err ? `audio error code ${err.code}` : "unknown audio error");
    });
  }

  /** Load `url` at `initialPosition` and (by default) begin playing. */
  async load(url: string, options: PlayerLoadOptions): Promise<void> {
    this.endedFired = false;
    this.host.onState("loading");
    this.host.onStreamUrlChanged(url);
    this.audio.src = url;
    this.audio.load();
    if (options.initialPosition > 0) {
      // Seek once metadata is ready so currentTime sticks.
      const seekWhenReady = () => {
        this.audio.currentTime = options.initialPosition;
        this.audio.removeEventListener("loadedmetadata", seekWhenReady);
      };
      this.audio.addEventListener("loadedmetadata", seekWhenReady);
    }
    await this.play();
  }

  async play(): Promise<void> {
    try {
      await this.audio.play();
    } catch (e) {
      this.host.onError(`play() rejected: ${String(e)}`);
    }
  }

  async pause(): Promise<void> {
    this.audio.pause();
  }

  async toggle(): Promise<boolean> {
    if (this.audio.paused) {
      await this.play();
      return true;
    }
    await this.pause();
    return false;
  }

  async seekElement(positionSeconds: number, _offset: number): Promise<void> {
    this.audio.currentTime = positionSeconds;
  }

  /** No transcode-reload concept for direct audio; treat as a fresh load. */
  async reloadSource(url: string, offset: number): Promise<void> {
    await this.load(url, {
      mediaId: "",
      mediaSourceId: null,
      needsTranscoding: false,
      initialPosition: offset,
      isLive: false,
      audioTrackIndex: null,
      knownDuration: 0,
      subtitleTracks: [],
    });
  }

  attach(_element: HTMLVideoElement | null): void {
    // The audio element is owned by the controller, not attached here.
  }

  setVolume(volume: number): void {
    this.audio.volume = Math.max(0, Math.min(1, volume));
  }

  setMuted(muted: boolean): void {
    this.audio.muted = muted;
  }

  async selectSubtitle(_streamIndex: number | null, _arrayIndex?: number): Promise<void> {
    // No subtitles for audio-only playback.
  }

  getPosition(): number {
    return this.audio.currentTime;
  }

  async dispose(): Promise<void> {
    this.audio.pause();
    this.audio.removeAttribute("src");
    this.audio.load();
  }
}
