import videojs from "video.js";
import "video.js/dist/video-js.css";
// Self-registers the "eme" plugin on the Video.js prototype (player.eme()).
// Must be bundled here rather than relying on the host page to load it
// separately: drm-config.js's HTML5 DRM path depends on `player.eme`
// existing, and that path is now also used as the fallback whenever Shaka
// is unavailable for the current browser/source (see _willUseShakaTech()).
import "videojs-contrib-eme";
import { setupDRM } from "./drm-config";
import CustomControlBar from "./components/CustomControlBar";
import ErrorOverlay from "./components/ErrorOverlay";
import ReplayOverlay from "./components/ReplayOverlay";
import Shaka from "./shaka/shaka.js";
import "./styles/styles.css";
import {
  clearPlayerError,
  createLocalizedError,
  ERROR_MESSAGE_KEYS,
  normalizePlayerError,
  reportPlayerError,
} from "./helpers/errorMessages.js";
import {
  DISPLAY_FORMAT,
  DISPLAY_FORMAT_VALUES,
} from "./constants/displayFormat.js";
import { DEFAULT_BANDWIDTH_ESTIMATE } from "./constants/bandwidth.js";
import { detectSourceType } from "./helpers/helper.js";
import {
  formatAspectRatioFromDimensions,
  normalizeAspectRatio,
} from "./helpers/aspectRatio.js";
import { setLogLevel, Logger } from "./utils/logger.js";
import {
  applyPlayerLanguage,
  registerAllLanguages,
  resolveLanguage,
} from "./lang";

registerAllLanguages();

// Define default values for the plugin's `state` object here.
Shaka.defaultState = {};

// Register tech with Video.js
videojs.registerTech("shaka", Shaka);
videojs.registerComponent("CustomControlBar", CustomControlBar);

class VideoJSDRMPlayer {
  constructor(options = {}) {
    // Validate required parameters
    if (!options.containerId) {
      throw new Error("containerId parameter is required");
    }

    if (!options.source || typeof options.source !== "string" || !options.source.trim()) {
      throw new Error("source parameter is required");
    }

    // Check if drmConfig is required
    if (
      options.isDRM &&
      (!options.drmConfig || !Object.keys(options.drmConfig).length)
    ) {
      throw new Error("drmConfig parameter is required if isDRM is true");
    }

    this.options = {
      // Default options
      totalWidth: "100%",
      totalHeight:
        options.audioOnlyMode && options.classicControls ? "" : "auto",
      isDRM: false,
      useShakaTech: false,
      // Prefer MSE/MMS over Safari's native HLS so the player can expose a
      // full quality/rendition list. Applied on whichever tech is active:
      //   - HTML5/VHS → experimentalUseMMS + overrideNative (desktop Safari)
      //   - Shaka     → streaming.useNativeHlsForFairPlay=false (Safari
      //                 FairPlay via MSE/EME or ManagedMediaSource)
      // Default true (same as the former fairplayUseMSE). Pass false to
      // fall back to native HLS. Largely a no-op outside Safari HLS.
      //
      // Exception (HTML5 FairPlay on iOS): legacy FairPlay
      // (com.apple.fps.1_0 + initLegacyFairplay / webkitneedkey) requires
      // Safari's native HLS. Forcing VHS+MMS there leaves encrypted
      // playback buffering forever with no license request. Desktop/Mac
      // Safari is left alone — overrideNativeHls stays honored there.
      overrideNativeHls: true,
      displayFormat: DISPLAY_FORMAT.DEFAULT,
      audioOnlyMode: false,
      classicControls: false,
      controls: true,
      // 3 = warnings/errors only. Info logs can be re-enabled at runtime
      // from the browser console via window.VJSD_DEBUG = 1 (or
      // localStorage.setItem("VJSD_DEBUG", "1")), or by passing logLevel: 1.
      logLevel: 3,
      language: "ja",
      fluid: false,
      disableContextMenu: true,
      defaultBandwidthEstimate: DEFAULT_BANDWIDTH_ESTIMATE,
      // Merge with user options
      ...options,
    };

    this.options.language = resolveLanguage(this.options.language);
    this._useDefaultAspectRatio = false;
    this._layoutModeNotes = this._resolveLayoutModeOptions();

    // Browser-level Shaka capability, decided once (Shaka.isSupported()
    // delegates to shaka.Player.isBrowserSupported(), which covers MSE,
    // ManagedMediaSource, and native-HLS Safari — including iPhone).
    // This must NOT be re-derived from `this.options.useShakaTech` alone:
    // that flag only records what the caller *requested*. Using the
    // requested flag as if it were the outcome previously made DRM setup
    // silently no-op when the tech Video.js actually constructed differed
    // from the requested one.
    this._shakaCapableBrowser =
      Boolean(this.options.useShakaTech) && Shaka.isSupported();

    // Set global log level
    if (this.options.logLevel !== undefined) {
      setLogLevel(this.options.logLevel);
    }

    // Create logger instance for this player
    this.logger = new Logger(`VideoJS-${options.containerId}`, "player");

    this.containerId = options.containerId;
    this.source = options.source;
    this.isDRM = options.isDRM ?? false;
    this.player = null;

    // Create video element
    this.createVideoElement();

    // Initialize Video.js player
    this.initializePlayer();
  }

  createVideoElement() {
    const containerElement = document.getElementById(this.containerId);

    if (!containerElement) {
      throw new Error(
        `Container element with id "${this.containerId}" not found`
      );
    }

    containerElement.classList.add("videojs-drm-host");
    if (this._isFluidLayout()) {
      containerElement.classList.add("videojs-drm-host--fluid");
    }

    // Create video element inside container
    const videoElement = document.createElement("video");
    videoElement.id = `${this.containerId}-video`;
    videoElement.className = "video-js vjs-default-skin";
    if (!this.options.classicControls) {
      videoElement.classList.add("hide-videojs-controls");
    }
    videoElement.setAttribute("playsinline", true);
    videoElement.setAttribute("webkit-playsinline", true);
    videoElement.setAttribute("x-webkit-airplay", "allow");

    // Clear container and add video element
    containerElement.innerHTML = "";
    this.stageElement = null;

    if (this._isFluidLayout()) {
      const stageElement = document.createElement("div");
      stageElement.className = "videojs-drm-stage";
      stageElement.appendChild(videoElement);
      containerElement.appendChild(stageElement);
      this.stageElement = stageElement;
    } else {
      containerElement.appendChild(videoElement);
    }

    this.dispose();

    this.videoElement = videoElement;
    this._setupContextMenuBlock();

    if (this.options.audioOnlyMode) {
      this.videoElement.classList.add("audio-only");

      if (!this.options.classicControls) {
        if (DISPLAY_FORMAT_VALUES.includes(this.options.displayFormat)) {
          this.videoElement.classList.add(
            `display-format-${this.options.displayFormat}`
          );
  
          if (this.options.displayFormat === DISPLAY_FORMAT.HIDDEN) {
            this.videoElement.style.display = "none";
          }
        } else {
          this.logger.warn(`Unknown displayFormat: ${this.options.displayFormat}`);
        }
      }
    }
  }

  initializePlayer() {
    const playerOptions = {
      ...this.getVideoJSOptions(),
      techOrder: this.options.useShakaTech ? ["shaka", "html5"] : ["html5"],
    };

    try {
      this.player = videojs(this.videoElement, playerOptions);
      applyPlayerLanguage(this.player, this.options.language);

      if (!this.player.getChild("ErrorOverlay")) {
        this.player.addChild("ErrorOverlay");
      }
      if (!this.player.getChild("ReplayOverlay")) {
        // displayFormat is stripped from Video.js options before construction;
        // pass the wrapper's resolved values explicitly.
        this.player.addChild("ReplayOverlay", {
          audioOnlyMode: this.options.audioOnlyMode,
          displayFormat: this.options.displayFormat,
        });
      }
    } catch (error) {
      this.logger.error("Failed to initialize Video.js player:", error);
      return;
    }

    if (!this.player) {
      throw new Error("Failed to initialize Video.js player");
    }

    window.addEventListener("beforeunload", function () {
      this.dispose();
    });

    this.player.on("error", () => {
      const payload = normalizePlayerError(this.player.error());
      this.logger.error("Player error:", payload);
      this.emit("error", payload);
    });

    // Add ready event
    this.player.ready(() => {
      this._applyPlayerRootDimensions();

      this.logger.info("Video.js Player is ready");
      if (this.options.aspectRatio) {
        this.logger.info(`aspectRatio: ${this.options.aspectRatio}`);
      }

      // Setup DRM if needed (also decides/handles Shaka tech wiring internally)
      try {
        this._configureSource(this.source, this.options.drmConfig);
      } catch (error) {
        this.logger.error("Error setting up source:", error);
        this._handlePlayerError(error);
      }

      this.emit("ready");

      this._addCustomControlBar();
      this._setupFluidFullscreenControlBarPlacement();

      // Emit custom ended event when playback finishes
      this.player.on("ended", () => {
        // this.logger.info("Playback ended");
        this.emit("ended");
      });

      this.player.on("loadedmetadata", () => {
        this._applyIntrinsicAspectRatioFromMetadata();
        this._applyAudioOnlyPlayButtonPoster();
      });
    });
  }

  /**
   * Decide whether Shaka will actually handle a given source, mirroring the
   * same checks Video.js itself uses when selecting a tech
   * (isSupported() && canPlaySource() !== ""). Computing this ourselves
   * lets us pick the correct DRM setup path (setupShakaDRM vs.
   * setupDRM/contrib-eme) *before* calling player.src(), instead of
   * assuming the requested `useShakaTech` option always matches the tech
   * Video.js ends up using.
   */
  _willUseShakaTech(source) {
    if (!this._shakaCapableBrowser) {
      return false;
    }
    const type = detectSourceType(source);
    return Shaka.canPlaySource({ type }, type) !== "";
  }

  /**
   * Single source of truth for wiring a source (initial load or
   * loadSource()) to the correct playback/DRM path:
   *   - Shaka tech, when this browser+source combination actually supports it
   *   - HTML5 + videojs-contrib-eme DRM path otherwise (this is also the
   *     fallback used when useShakaTech was requested but Shaka is not
   *     usable here — e.g. Shaka.isSupported() is false, or Shaka cannot
   *     play this source type)
   *   - plain player.src() when DRM is not enabled
   *
   * Never silently drops DRM configuration: if isDRM is true, one of the
   * two DRM setup paths always runs.
   */
  _configureSource(source, drmConfig) {
    const type = detectSourceType(source);
    const willUseShaka = this._willUseShakaTech(source);

    if (willUseShaka) {
      const tech = this.player.tech(true);
      if (tech && typeof tech.setContainerId === "function") {
        tech.setContainerId(this.containerId);
      }
      if (tech && tech.options_ && this.isDRM && drmConfig) {
        tech.options_.drmConfig = drmConfig;
      }
      this.logger.info("Routing playback through Shaka tech", {
        type,
        isDRM: this.isDRM,
      });
      this.player.src({ src: source, type });
      return;
    }

    if (this.isDRM && drmConfig) {
      if (this.options.useShakaTech) {
        this.logger.warn(
          "useShakaTech was requested but Shaka is unavailable for this browser/source (Shaka.isSupported() is false, or Shaka cannot play this source type). Falling back to the HTML5 + videojs-contrib-eme DRM path instead of silently skipping DRM setup.",
          { type, activeTechFallback: "html5" }
        );
      }
      this.logger.info("Routing playback through HTML5 + videojs-contrib-eme DRM path", {
        type,
      });
      setupDRM(
        this.player,
        { url: source, type },
        drmConfig,
        this.logger,
        this.containerId
      );
      return;
    }

    this.player.src({ src: source, type });
  }

  _applyIntrinsicAspectRatioFromMetadata() {
    if (!this._useDefaultAspectRatio || !this.player) {
      return;
    }

    const ratio = formatAspectRatioFromDimensions(
      this.player.videoWidth(),
      this.player.videoHeight()
    );

    if (!ratio || ratio === this.options.aspectRatio) {
      return;
    }

    this.options.aspectRatio = ratio;
    this.player.aspectRatio(ratio);
    this.logger.info(`aspectRatio updated from metadata: ${ratio}`);
  }

  /**
   * Apply poster to play button for audio-only + PLAY_PAUSE_ONLY mode.
   * Used on initial load and after loading a new source (when control bar is recreated).
   */
  _applyAudioOnlyPlayButtonPoster() {
    // Wait for 500ms to ensure metadata is loaded (for Safari)
    setTimeout(() => {
      try {
        if (
          !this.options.classicControls &&
          this.options.audioOnlyMode &&
          this.options.controls &&
          this.options.displayFormat === DISPLAY_FORMAT.PLAY_PAUSE_ONLY &&
          this.options.poster
        ) {
          const playButton = this.player
            .el()
            .querySelector(".videojs-drm-play-button");
          if (playButton) {
            playButton.style.background = null;
            playButton.style.backgroundRepeat = "no-repeat";
            playButton.style.backgroundPosition = "center";
            playButton.style.backgroundSize = "cover";
            playButton.style.backgroundImage = `url(${this.options.poster})`;
          }
        }
      } catch (e) {
        this.logger.error("Error applying audio-only mode:", e);
      }
    }, 500);
  }

  /**
   * Video.js uses fluid layout for aspect-ratio sizing (padding-top box).
   * Keep package DOM (stage, control bar) aligned with resolved player mode.
   */
  _isFluidLayout() {
    return Boolean(this.options.fluid);
  }

  /**
   * Apply totalWidth/totalHeight to the player root (.video-js wrapper), not the
   * <video> tech element. Video.js may copy inline size from <video> during init.
   */
  _applyPlayerRootDimensions() {
    if (!this.player || this._isFluidLayout()) {
      return;
    }

    const rootEl = this.player.el();
    if (this.options.totalWidth) {
      rootEl.style.width = this.options.totalWidth;
    }
    if (this.options.totalHeight) {
      rootEl.style.height = this.options.totalHeight;
    }

    const techEl = this.player.tech(true)?.el();
    if (techEl) {
      techEl.style.removeProperty("width");
      techEl.style.removeProperty("height");
    }
  }

  /**
   * Match Video.js layout rules:
   * - fluid + fill together → fluid wins, fill is disabled
   * - aspectRatio (only when caller provides it) → enables fluid (and
   *   disables fill), same as player.aspectRatio()
   * No package-level default aspectRatio: without one, fluid stays at the
   * caller's value (default false) and layout uses the non-fluid DOM
   * (video directly in host, control bar inside .video-js).
   */
  _resolveLayoutModeOptions() {
    const notes = {};
    const hasExplicitAspectRatio =
      this.options.aspectRatio !== undefined &&
      this.options.aspectRatio !== null &&
      this.options.aspectRatio !== "";

    if (hasExplicitAspectRatio) {
      this.options.aspectRatio = normalizeAspectRatio(this.options.aspectRatio);
      this.options.fluid = true;

      if (this.options.fill) {
        this.options.fill = false;
        notes.disabledFillForAspectRatio = true;
      }
    }

    if (this.options.fluid && this.options.fill) {
      this.options.fill = false;
      notes.disabledFillForFluid = true;
    }

    return notes;
  }

  getVideoJSOptions() {
    const videojsOptions = { ...this.options };

    // Remove our custom options
    delete videojsOptions.containerId;
    delete videojsOptions.source;
    delete videojsOptions.isDRM;
    delete videojsOptions.totalWidth;
    delete videojsOptions.totalHeight;
    delete videojsOptions.displayFormat;
    delete videojsOptions.classicControls;
    delete videojsOptions.logLevel;
    delete videojsOptions.disableContextMenu;
    delete videojsOptions.overrideNativeHls;

    // Use the resolved browser-level capability, not the raw requested flag:
    // when useShakaTech is requested but this browser can't run Shaka (e.g.
    // iPhone Safari), Video.js will actually construct the "html5" tech, so
    // it must receive the html5/vhs options below (ABR bandwidth, MMS),
    // not the (unused) shaka options.
    if (this._shakaCapableBrowser) {
      videojsOptions.shaka = {
        ...(videojsOptions.shaka || {}),
      };
      if (this.options.defaultBandwidthEstimate) {
        videojsOptions.shaka.defaultBandwidthEstimate = this.options.defaultBandwidthEstimate;
      }
      if (this.options.isDRM) {
        videojsOptions.shaka.drmConfig = this.options.drmConfig;
      }
      if (this.options.overrideNativeHls !== undefined) {
        videojsOptions.shaka.overrideNativeHls = this.options.overrideNativeHls;
      }
      delete videojsOptions.drmConfig;
      delete videojsOptions.defaultBandwidthEstimate;
    } else {
      delete videojsOptions.defaultBandwidthEstimate;
      const html5Vhs = {
        ...(videojsOptions.vhs || {}),
        ...(videojsOptions.html5?.vhs || {}),
      };
      if (html5Vhs.bandwidth === undefined) {
        html5Vhs.bandwidth = this.options.defaultBandwidthEstimate;
      }

      // HTML5 FairPlay on iOS must keep native HLS so webkitneedkey /
      // com.apple.fps.1_0 can run. Mac/desktop Safari continues to honor
      // overrideNativeHls (MSE/MMS) — unchanged from previous behavior.
      const html5OverrideNativeHls = this._shouldOverrideNativeHlsForHtml5();

      if (html5OverrideNativeHls) {
        if (html5Vhs.experimentalUseMMS === undefined) {
          html5Vhs.experimentalUseMMS = true;
        }
        if (html5Vhs.overrideNative === undefined) {
          html5Vhs.overrideNative = true;
        }
      }

      const html5Options = {
        ...(videojsOptions.html5 || {}),
        vhs: html5Vhs,
      };

      if (html5OverrideNativeHls && html5Vhs.overrideNative) {
        if (html5Options.nativeAudioTracks === undefined) {
          html5Options.nativeAudioTracks = false;
        }
        if (html5Options.nativeVideoTracks === undefined) {
          html5Options.nativeVideoTracks = false;
        }
      }

      videojsOptions.html5 = html5Options;

      delete videojsOptions.vhs;
      delete videojsOptions.drmConfig;
    }

    return videojsOptions;
  }

  /**
   * Decide whether the HTML5/VHS path should override Safari native HLS.
   *
   * On iOS, legacy FairPlay (com.apple.fps.1_0 + initLegacyFairplay) depends
   * on native HLS firing webkitneedkey. Forcing VHS + ManagedMediaSource
   * there commonly produces endless buffering with no license request.
   * Desktop/Mac Safari is unaffected: when overrideNativeHls is true we
   * still enable MSE/MMS as before.
   */
  _shouldOverrideNativeHlsForHtml5() {
    if (!this.options.overrideNativeHls) {
      return false;
    }

    const isIos = Boolean(videojs.browser?.IS_IOS);
    const hasFairplay =
      Boolean(this.options.isDRM) && Boolean(this.options.drmConfig?.fairplay);

    if (isIos && hasFairplay) {
      this.logger.info(
        "iOS HTML5 FairPlay: keeping Safari native HLS (not overriding with VHS/MMS) so legacy FairPlay webkitneedkey can run",
        {
          overrideNativeHlsRequested: true,
          html5OverrideNative: false,
        }
      );
      return false;
    }

    return true;
  }

  _handlePlayerError(error) {
    if (!this.player) {
      return;
    }
    reportPlayerError(this.player, error);
  }

  _hidePlayerError() {
    clearPlayerError(this.player);
  }

  // Custom event system
  emit(eventName, data = null) {
    const event = new CustomEvent(
      `${this.containerId}-videojs-drm-player:${eventName}`,
      {
        detail: data,
      }
    );
    document.dispatchEvent(event);
  }

  on(eventName, callback) {
    document.addEventListener(
      `${this.containerId}-videojs-drm-player:${eventName}`,
      callback
    );
  }

  // Public API methods
  play() {
    if (this.player) {
      return this.player.play();
    }
  }

  pause() {
    if (this.player) {
      this.player.pause();
    }
  }

  stop() {
    if (this.player) {
      this.player.pause();
      this.player.currentTime(0);
    }
  }

  rewind(seconds = 10) {
    if (this.player) {
      const currentTime = this.player.currentTime();
      this.player.currentTime(Math.max(0, currentTime - seconds));
    }
  }

  forward(seconds = 10) {
    if (this.player) {
      const currentTime = this.player.currentTime();
      const duration = this.player.duration();
      this.player.currentTime(Math.min(duration, currentTime + seconds));
    }
  }

  setVolume(volume) {
    if (this.player && volume >= 0 && volume <= 1) {
      this.player.volume(volume);
    }
  }

  getVolume() {
    return this.player ? this.player.volume() : 0;
  }

  isMuted() {
    return this.player ? this.player.muted() : false;
  }

  mute() {
    if (this.player) {
      this.player.muted(true);
    }
  }

  unmute() {
    if (this.player) {
      this.player.muted(false);
    }
  }

  toggleMute() {
    if (this.player) {
      this.player.muted(!this.player.muted());
    }
  }

  getCurrentTime() {
    return this.player ? this.player.currentTime() : 0;
  }

  getDuration() {
    return this.player ? this.player.duration() : 0;
  }

  seek(time) {
    if (this.player && time >= 0) {
      this.player.currentTime(time);
    }
  }

  setSize(width, height) {
    if (!this.player) {
      return;
    }

    this.options.totalWidth = width;
    this.options.totalHeight = height;
    this._applyPlayerRootDimensions();
  }

  /**
   * Mount custom control bar.
   * - Non-fluid: stays inside player flow (adjacent layout, no absolute overlap).
   * - Fluid: DOM is moved below the fluid stage so Video.js aspect ratio stays native.
   */
  _addCustomControlBar() {
    if (this.options.classicControls || !this.options.controls || !this.player) {
      return null;
    }

    const controlBar = this.player.addChild("CustomControlBar");
    this._placeCustomControlBar(controlBar);
    return controlBar;
  }

  _placeCustomControlBar(controlBar) {
    if (!controlBar?.el) {
      return;
    }

    if (
      this._isFluidLayout() &&
      this.stageElement &&
      this.player &&
      !this.player.isFullscreen()
    ) {
      this.stageElement.insertAdjacentElement("afterend", controlBar.el());
      return;
    }

    const playerEl = this.player.el();
    if (controlBar.el().parentElement !== playerEl) {
      playerEl.appendChild(controlBar.el());
    }
  }

  /**
   * Fluid layout keeps the bar below the stage in normal view. In fullscreen,
   * reuse the non-fluid approach: bar inside .video-js with absolute bottom.
   */
  _syncFluidControlBarPlacement() {
    if (!this._isFluidLayout() || !this.stageElement || !this.player) {
      return;
    }

    const controlBar = this.player.getChild?.("CustomControlBar");
    if (!controlBar?.el) {
      return;
    }

    this._placeCustomControlBar(controlBar);
  }

  _setupFluidFullscreenControlBarPlacement() {
    if (
      !this._isFluidLayout() ||
      !this.stageElement ||
      !this.player ||
      this._fluidFullscreenPlacementBound
    ) {
      return;
    }

    this._fluidFullscreenPlacementBound = true;
    const handler = () => this._syncFluidControlBarPlacement();
    this.player.on("fullscreenchange", handler);
    this.player.on("enterfullscreen", handler);
    this.player.on("exitfullscreen", handler);
  }

  /**
   * Remove existing CustomControlBar (if any) and add a new one.
   * Used when loading a new source so controls reflect the new media.
   * Also wires one-time loadedmetadata to apply audio-only play button poster for the new source.
   */
  _recreateCustomControlBar() {
    if (this.options.classicControls || !this.options.controls) {
      return;
    }
    const existing = this.player.getChild && this.player.getChild("CustomControlBar");
    if (existing) {
      this.player.removeChild(existing);
      if (typeof existing.dispose === "function") {
        existing.dispose();
      }
    }
    this._addCustomControlBar();
    this._syncFluidControlBarPlacement();
    this.logger.info("Custom control bar recreated for new source");

    this.player.one("loadedmetadata", () => {
      this._applyIntrinsicAspectRatioFromMetadata();
      this._applyAudioOnlyPlayButtonPoster();
    });
  }

  /**
   * Load a new source into the current player instance.
   * Only updates source and DRM configuration; all other options remain unchanged.
   * Recreates the custom control bar for the new source.
   *
   * @param {string} source - New media source URL
   * @param {object} drmConfig - New DRM configuration (optional)
   */
  loadSource(source, drmConfig = this.options.drmConfig) {
    if (!source) {
      throw new Error("source parameter is required");
    }

    if (!this.player) {
      this.logger.warn("Cannot load source: player is not initialized");
      this._handlePlayerError(
        createLocalizedError(ERROR_MESSAGE_KEYS.playerNotInitialized)
      );
      return;
    }

    this._hidePlayerError();

    // Update only fields related to source & DRM
    this.source = source;
    if (drmConfig) {
      this.isDRM = true;
      this.options.isDRM = this.isDRM;
      this.options.drmConfig = drmConfig;
    }

    try {
      // Shared with initializePlayer(): picks Shaka vs. HTML5+contrib-eme
      // DRM vs. plain src() based on actual Shaka capability for this
      // source, not just the requested useShakaTech flag.
      this._configureSource(this.source, this.options.drmConfig);
      this._recreateCustomControlBar();
    } catch (error) {
      this.logger.error("Error loading new source:", error);
      this._handlePlayerError(error);
    }
  }

  _setupContextMenuBlock() {
    if (!this.options.disableContextMenu) {
      return;
    }

    this._onContextMenu = (event) => {
      event.preventDefault();
    };

    this._contextMenuTargets = [];

    const host = document.getElementById(this.containerId);
    if (host) {
      host.addEventListener("contextmenu", this._onContextMenu);
      this._contextMenuTargets.push(host);
    }
  }

  _teardownContextMenuBlock() {
    if (!this._onContextMenu || !this._contextMenuTargets) {
      return;
    }

    for (const target of this._contextMenuTargets) {
      target.removeEventListener("contextmenu", this._onContextMenu);
    }

    this._contextMenuTargets = [];
    this._onContextMenu = null;
  }

  // Cleanup method
  dispose() {
    this._teardownContextMenuBlock();

    if (this.player && !this.player.isDisposed()) {
      this.player.dispose();
      // this.player = null;
    }

    // Don't remove the video element since it's provided by the user
    this.videoElement = null;
  }
}

// Export for different module systems
export default VideoJSDRMPlayer;

// Also make it available globally if not using modules
if (typeof window !== "undefined") {
  window.VideoJSDRMPlayer = VideoJSDRMPlayer;
}
