import videojs from "video.js";
import shaka from "shaka-player";
import { setupShakaDRM } from "./shaka-drm-config";
import setupQualityTracks from "./setup-quality-tracks";
import setupTextTracks from "./setup-text-tracks";
import setupAudioTracks from "./setup-audio-tracks";
import { Logger, LOG_LEVELS, getLogLevel } from "../utils/logger.js";

const Html5 = videojs.getTech("Html5");

// Expose Shaka library globally (only once)
if (typeof window !== "undefined" && !window.__shaka) {
  window.__shaka = shaka;
  const initLogger = new Logger("Shaka-Init");
  initLogger.info("Exposed Shaka library to window.__shaka");
  initLogger.info("Version:", shaka.Player.version);
}

class Shaka extends Html5 {
  constructor(options, ready) {
    super(options, ready);

    this.vjsPlayer = videojs(options.playerId);
    this.shaka_ = null;
    // Create logger instance for this tech
    this.logger = new Logger(`Shaka-${this.getContainerTag() || 'Tech'}`);

    this.player_.ready(() => {
      this.player_.addClass("vjs-shaka");
    });
  }

  static isSupported() {
    return !!window.MediaSource && shaka.Player.isBrowserSupported();
  }

  static canPlaySource(sourceObj, type) {
    const dashTypeRE = /^(application\/dash\+xml|application\/x-mpegURL)/i;

    if (dashTypeRE.test(sourceObj.type)) {
      return "probably";
    }

    return "";
  }

  createEl() {
    this.el_ = Html5.prototype.createEl.apply(this, arguments);

    // Install built-in polyfills to patch browser incompatibilities.
    shaka.polyfill.installAll();

    // Set debug log level
    if (shaka.log) {
      if (this.options_.debug) {
        shaka.log.setLevel(shaka.log.Level.DEBUG);
      } else {
        shaka.log.setLevel(shaka.log.Level.ERROR);
      }
    }

    this.el_.tech = this;

    return this.el_;
  }

  // Helper method to get container tag for logging
  getContainerTag() {
    return this.containerId_ ? `[${this.containerId_}]` : "";
  }

  async initializeShaka() {
    if (!this.shaka_ && this.el_) {
      this.logger.info("Initializing Shaka Player...");
      try {
        this.shaka_ = new shaka.Player();

        this.shaka_.getNetworkingEngine().registerResponseFilter((type, response) => {
          if (type === shaka.net.NetworkingEngine.RequestType.MANIFEST) {
            const decoder = new TextDecoder('utf-8');
            let manifestText = decoder.decode(response.data);
            manifestText = manifestText.replace(/GROUP-ID="audio-[^"]*"/g, 'GROUP-ID="audio-main"');
            manifestText = manifestText.replace(/AUDIO="audio-[^"]*"/g, 'AUDIO="audio-main"');

            const encoder = new TextEncoder();
            response.data = encoder.encode(manifestText);
          }
        });
        await this.shaka_.attach(this.el_);
        this.shaka_.configure('streaming.maxDisabledTime', 0);

        this.logger.info('Shaka configuration:', this.shaka_.getConfiguration());

        // Log ABR config + restrictions after configuration
        this.logger.info('Effective config abr:', this.shaka_.getConfiguration().abr);
        this.logger.info('Effective config restrictions:', this.shaka_.getConfiguration().restrictions);

        this.logger.info('Player initialized');
      } catch (error) {
        this.logger.error('Error initializing Shaka Player:', error);
      }
    } else if (!this.el_) {
      this.logger.warn('Element not created, cannot initialize Shaka Player');
    }
  }

  async setSrc(src) {
    const me = this;

    // Ensure element and Shaka Player are initialized
    if (!this.el_) {
      this.logger.info("Element has not been created, waiting...");
      // Add retry up to 3 times when waiting for this.el_
      if (typeof this._elRetryCount === "undefined") {
        this._elRetryCount = 0;
      }
      this._elRetryCount++;
      if (this._elRetryCount > 3) {
        this.logger.error(
          "Cannot create element for Shaka Player after 3 retries."
        );
        return;
      }
      setTimeout(() => {
        me.setSrc(src);
      }, 100);
      return;
    }

    if (!this.shaka_) {
      await this.initializeShaka();
    }

    if (!this.shaka_) {
      this.logger.warn("Cannot initialize Shaka Player");
      return;
    }

    const shakaOptions = this.options_.configuration || {};

    if (!shakaOptions.abr) {
      shakaOptions.abr = {
        enabled: true,
      };
    }

    if (this.options_.drmConfig) {
      try {
        await setupShakaDRM(this.shaka_, this.options_.drmConfig, this.logger);
      } catch (error) {
        this.logger.error("Error setting up Shaka DRM:", error);
      }
    }

    // Log load request
    this.logger.info("Load request:", {
      uri: src,
      isHls: src.includes('.m3u8')
    });

    // Log ABR config + restrictions before load
    this.logger.info("Effective config abr:", this.shaka_.getConfiguration().abr);
    this.logger.info("Effective config restrictions:", this.shaka_.getConfiguration().restrictions);

    this.shaka_.addEventListener("buffering", function (event) {
      if (event.buffering) {
        me.vjsPlayer.trigger("waiting");
      } else {
        me.vjsPlayer.trigger("playing");
      }
    });

    // Error logging
    this.shaka_.addEventListener("error", function (event) {
      me.logger.error("Player error event:", event.detail);
      try {
        me.retriggerError(event.detail);
      } catch (error) {
        me.logger.error("Error in retriggerError:", error);
      }
    });

    // Adaptation event logging
    this.shaka_.addEventListener("adaptation", function () {
      me.logger.info("adaptation event fired");
      me.logVariants(me.shaka_, '(after adaptation)');
    });

    this.shaka_
      .load(src)
      .then(function () {
        me.initShakaMenus();

        // Log variants at startup
        me.logVariants(me.shaka_, '(at startup)');

        // Start stats logging (first 30 seconds)
        me.startStatsLogging(me.shaka_);

        // Expose instances after source is loaded
        if (me.containerId_) {
          me.exposeShakaInstances();
        }
      })
      .catch(function (error) {
        me.logger.error("Error loading source:", error);
        try {
          me.retriggerError(error);
        } catch (retriggerError) {
          me.logger.error("Error in retriggerError:", retriggerError);
        }
      });
  }

  dispose() {
    // Clear stats logging timer
    if (this._statsTimer) {
      clearInterval(this._statsTimer);
      this._statsTimer = null;
    }

    if (this.shaka_) {
      this.logger.info('Unloading Shaka Player');

      // Detach Shaka player from element BEFORE unloading
      // This ensures the element is still valid for child components
      try {
        this.shaka_.detach();
      } catch (e) {
        // Ignore if already detached or if there is an error
        this.logger.warn('Error detaching Shaka player:', e);
      }

      // Unload and destroy Shaka player
      try {
        this.shaka_.unload();
      } catch (e) {
        this.logger.warn('Error unloading Shaka player:', e);
      }

      try {
        this.shaka_.destroy();
      } catch (e) {
        this.logger.warn('Error destroying Shaka player:', e);
      }

      this.shaka_ = null;
      // super.dispose();
    }
  }

  initShakaMenus() {
    // Setup quality tracks, text tracks, audio tracks
    this.logger.info('Setting up Shaka menus...');

    try {
      // Setup quality tracks (renditions)
      setupQualityTracks(this, this.shaka_);
      this.logger.info('Quality tracks setup completed');

      // Setup text tracks (subtitles)
      setupTextTracks(this, this.shaka_);
      this.logger.info('Text tracks setup completed');

      // Setup audio tracks
      setupAudioTracks(this, this.shaka_);
      this.logger.info('Audio tracks setup completed');
    } catch (error) {
      this.logger.error('Error setting up Shaka:', error);
    }
  }

  retriggerError(event) {
    let code;

    // map the shaka player error to the appropriate video.js error
    if (
      event.message &&
      (event.message.indexOf("UNSUPPORTED") > -1 ||
        event.message.indexOf("NOT_SUPPORTED") > -1)
    ) {
      code = 4;
    } else {
      switch (event.category) {
        case 1:
          code = 2;
          break;
        case 2:
        case 3:
        case 4:
          code = 3;
          break;
        case 5:
          code = 1;
          break;
        case 6:
          code = 5;
          break;
        case 7:
        case 8:
        case 9:
          code = 0;
          break;
      }
    }

    // For non-critical errors, let player try to recover
    const isCritical = event.severity == shaka.util.Error.Severity.CRITICAL;

    if (isCritical && getLogLevel() === LOG_LEVELS.ERROR) {
      try {
        this.vjsPlayer.error({
          code,
          message: `${event.code} - ${event.message}`,
        });
      } catch (error) {
        this.logger.error("Error setting player error state:", error);
      }
    } else {
      // For non-critical errors, just log and let player continue
      this.logger.warn("Non-critical Shaka error (player will try to recover):", {
        code,
        message: `${event.code} - ${event.message}`,
      });
    }
  }

  getShaka_() {
    return this.shaka_;
  }

  // Log available variants + currently active
  logVariants(player, tag = '') {
    if (!player) return;

    try {
      const variants = player.getVariantTracks();
      const rows = variants
        .map(t => ({
          id: t.id,
          bw: t.bandwidth,
          w: t.width,
          h: t.height,
          codecs: t.codecs,
          active: t.active,
          language: t.language,
        }))
        .sort((a, b) => a.bw - b.bw);

      this.logger.info(`Variants ${tag}`, rows);
    } catch (error) {
      this.logger.warn("Error logging variants:", error);
    }
  }

  // Log ABR stats periodically during playback (initial 30 seconds)
  startStatsLogging(player) {
    if (!player) return;

    // Clear any existing timer
    if (this._statsTimer) {
      clearInterval(this._statsTimer);
      this._statsTimer = null;
    }

    let statsCount = 0;
    const maxStatsLogs = 15; // 30 seconds / 2 seconds = 15 logs

    this._statsTimer = setInterval(() => {
      try {
        const s = player.getStats();
        this.logger.info("Stats", {
          estBw: s.estimatedBandwidth,
          playTime: s.playTime,
          bufferingTime: s.bufferingTime,
          variantBw: s.streamBandwidth,
          droppedFrames: s.droppedFrames,
        });

        statsCount++;
        if (statsCount >= maxStatsLogs) {
          clearInterval(this._statsTimer);
          this._statsTimer = null;
        }
      } catch (error) {
        this.logger.warn("Error getting stats:", error);
        clearInterval(this._statsTimer);
        this._statsTimer = null;
      }
    }, 2000);
  }

  // Set container ID for exposure
  setContainerId(containerId) {
    this.containerId_ = containerId;
    // Update logger prefix to reflect the container ID
    if (this.logger && containerId) {
      this.logger.setPrefix(`Shaka-${containerId}`);
    }
  }

  // Expose Shaka instances to window with container-id as key
  exposeShakaInstances() {
    if (typeof window === "undefined" || !this.containerId_ || !this.shaka_) {
      return;
    }

    const containerId = this.containerId_;

    // Initialize window.__shaka_players if not exists
    if (!window.__shaka_players) {
      window.__shaka_players = {};
    }
    if (!window.__shaka_videos) {
      window.__shaka_videos = {};
    }
    if (!window.__shaka_debug) {
      window.__shaka_debug = {};
    }

    // Expose instances with container-id as key
    window.__shaka_players[containerId] = this.shaka_;
    window.__shaka_videos[containerId] = this.el_;

    // Expose debug helpers
    window.__shaka_debug[containerId] = {
      player: this.shaka_,
      video: this.el_,
      tech: this,
      vjsPlayer: this.vjsPlayer,
      getManifest: () => this.shaka_.getManifest(),
      getConfiguration: () => this.shaka_.getConfiguration(),
      getStats: () => this.shaka_.getStats(),
      getBufferedInfo: () => this.shaka_.getBufferedInfo(),
      getPlaybackRate: () => this.shaka_.getPlaybackRate(),
      getVolume: () => this.shaka_.getVolume(),
      isLive: () => this.shaka_.isLive(),
      seekRange: () => this.shaka_.seekRange(),
    };

    // Log exposure
    this.logger.info(`Exposed instances for container "${containerId}":`, {
      __shaka: !!window.__shaka,
      __shaka_players: !!window.__shaka_players[containerId],
      __shaka_videos: !!window.__shaka_videos[containerId],
      __shaka_debug: !!window.__shaka_debug[containerId],
    });
  }
}

export default Shaka;
