import { AudioTrack, HTMLMediaElement, TextTrack, VideoPlayer, VideoTrack } from '@amazon-devices/react-native-w3cmedia/dist/headless';
  import {
    Event,
    EventListener,
    TrackEvent,
  } from '@amazon-devices/react-native-w3cmedia/dist/headless';
  import { ShakaPlayer } from './shakaplayer/ShakaPlayer';
  import {
    IHttpHeader,
    IPlayerSessionId,
    IPlayerSessionLoadParams,
    IPlayerSessionMediaInfo,
    IPlayerSessionPosition,
    IPlayerSessionState,
    IPlayerSessionStatus,
    ITimeRange,
    ITrackType,
    IViewHandle,
  } from '@amazon-devices/kepler-player-server';
  import {
    IPlayerServer,
    IPlayerServerHandler,
    IPlayerServerFactory,
    PlayerServerFactory,
  } from '@amazon-devices/kepler-player-server';
  class PlayerServerHandler implements IPlayerServerHandler {
    #playerService: PlayerService;
    constructor(playerService: PlayerService) {
      console.log('[PlayerService] VegaPlayerServerHandler ctor');
      this.#playerService = playerService;
    }
    handleLoad(mediaInfo: IPlayerSessionMediaInfo, loadParams?: IPlayerSessionLoadParams, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleLoad called with: ', {
        mediaInfo,
        loadParams,
        sessionId,
      });
      this.#playerService?.onLoad(mediaInfo, loadParams, sessionId);
    }
    handleUnload(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleUnload called with: ', {
        sessionId
      });
      this.#playerService?.onUnload();
    }
    handleSetVideoView(handle: IViewHandle, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleSetVideoView called with:', {
        handle,
        sessionId
      });
      this.#playerService?.onSurfaceViewCreated(handle.handle);
    }
    handleClearVideoView(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleClearVideoView called with sessionId:', sessionId);
      this.#playerService?.onSurfaceViewDestroyed();
    }
    handleSetTextView(handle: IViewHandle, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleSetVideoView called with:', {
        handle,
        sessionId
      });
      this.#playerService?.onCaptionViewCreated(handle.handle);
    }
    handleClearTextView(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleClearTextView called with:', {
        sessionId
      });
      this.#playerService?.onCaptionViewDestroyed(handle.handle);
    }
    handlePlay(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handlePlay called with sessionId:', sessionId);
      this.#playerService?.handlePlay();
    }
    handlePause(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handlePause called with sessionId:', sessionId);
      this.#playerService?.handlePause();
    }
    handleSeek(position: number, isRelative?: boolean, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleSeek called with:', {
        position,
        sessionId
      });
      this.#playerService?.handleSeek(position);
    }
    handleSetMute(isMuted: boolean, sessionId?: IPlayerSessionId): void {
      console.debug('[PlayerService] handleSetMute called with:', {
        sessionId
      });
      this.#playerService?.handleSetMute(isMuted);
    }
    handleSetPlaybackRate(playbackRate: number, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleSetPlaybackRate called with:', {
        sessionId
      });
    }
    handleSetVolume(volume: number, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleSetVolume called with:', {
        volume,
        sessionId
      });
      this.#playerService?.handleSetVolume(volume);
    }
    handleSetActiveTrack(trackType: ITrackType, trackId: string, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleSetActiveTrack called with:', {
        trackType,
        trackId,
        sessionId
      });
      this.#playerService.handleSetActiveTrack(String(trackType), trackId);
    }
    handleGetCurrentPosition(sessionId?: IPlayerSessionId): number {
      console.log('[PlayerService] handleGetCurrentPosition called with:', {
        sessionId
      });
      return this.#playerService.getCurrentPlaybackPosition().position;
    }
    handleMessage(message: any, sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleMessage called with:', {
        sessionId
      });
    }
    handleStartBufferedRangesUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStartBufferedRangesUpdates called with:', {
        sessionId
      });
    }
    handleStopBufferedRangesUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStopBufferedRangesUpdates called with:', {
        sessionId
      });
    }
    handleStartStatusUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStartStatusUpdates called with:', {
        sessionId
      });
    }
    handleStopStatusUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStopStatusUpdates called with:', {
        sessionId
      });
    }
    handleStartTrackUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStartTrackUpdates called with:', {
        sessionId
      });
    }
    handleStopTrackUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStopTrackUpdates called with:', {
        sessionId
      });
    }
    handleStartMessageUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStartMessageUpdates called with:', {
        sessionId
      });
    }
    handleStopMessageUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStopMessageUpdates called with:', {
        sessionId
      });
    }
    handleStartErrorUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStartErrorUpdates called with:', {
        sessionId
      });
    }
    handleStopErrorUpdates(sessionId?: IPlayerSessionId): void {
      console.log('[PlayerService] handleStopErrorUpdates called with:', {
        sessionId
      });
    }
  }
  class PlayerService {
    #playerServerFactory: IPlayerServerFactory | undefined;
    #playerServer: IPlayerServer | undefined;
    #playerServerHandler: IPlayerServerHandler | undefined;
    #msePlayer: ShakaPlayer | undefined;
    #videoPlayer: VideoPlayer | undefined;
    #serviceComponentId: string = "com.yourcompany.kepler.headlessjsmediaplayer.service";
    #activeSurfaceHandle: string | undefined;
    #activeCaptionHandle: string | undefined;
    #activeSessionId: IPlayerSessionId | undefined;
    #hasError: boolean = false;
    #AUTOPLAY: boolean = true;
    #initializeVideoPlayer = async (): Promise<void> => {
      console.log('[PlayerService] initializeVideoPlayer');
      this.#videoPlayer = new VideoPlayer();
      // @ts-ignore
      global.gmedia = this.#videoPlayer;
      await this.#videoPlayer.initialize();
      this.#setUpEventListeners();
      this.#videoPlayer.autoplay = this.#AUTOPLAY;
      this.#initialiseMsePlayer();
    };
    #initialiseMsePlayer = () => {
      console.log('[PlayerService] initialiseMsePlayer');
      if (this.#videoPlayer !== undefined) {
        this.#msePlayer = new ShakaPlayer(this.#videoPlayer as HTMLMediaElement, {
            secure: false,                    // Playback goes through secure or non-secure mode
            abrEnabled: true,                 // Enables Adaptive Bit-Rate (ABR) switching
            abrMaxWidth: 3840,   // Maximum width allowed for ABR
            abrMaxHeight: 2160, // Maximum height allowed for ABR
            startPosition: 0
          });
      }
    };
    #setUpEventListeners = (): void => {
      console.log('[PlayerService] setUpEventListeners');
      this.#videoPlayer?.addEventListener('play', this.#onPlay);
      this.#videoPlayer?.addEventListener('pause', this.#onPause);
      this.#videoPlayer?.addEventListener('seeked', this.#onSeeked);
      this.#videoPlayer?.addEventListener('ended', this.#onEnded);
      this.#videoPlayer?.addEventListener('error', this.#onError);
      this.#videoPlayer?.audioTracks.addEventListener('addtrack', this.#onAudioTrackAdded);
      this.#videoPlayer?.videoTracks.addEventListener('addtrack', this.#onVideoTrackAdded);
      this.#videoPlayer?.textTracks.addEventListener('addtrack', this.#onTextTrackAdded);
      this.#videoPlayer?.audioTracks.addEventListener('removetrack', this.#onAudioTrackRemoved);
      this.#videoPlayer?.videoTracks.addEventListener('removetrack', this.#onVideoTrackRemoved);
      this.#videoPlayer?.textTracks.addEventListener('removetrack', this.#onTextTrackRemoved);
      (this.#videoPlayer as HTMLMediaElement)?.addEventListener('waiting', this.#updateBufferedRanges);
      (this.#videoPlayer as HTMLMediaElement)?.addEventListener('canplay', this.#updateBufferedRanges);
    };
    #removeEventListeners = (): void => {
      console.log('[PlayerService] removeEventListeners');
      this.#videoPlayer?.removeEventListener('play', this.#onPlay);
      this.#videoPlayer?.removeEventListener('pause', this.#onPause);
      this.#videoPlayer?.removeEventListener('seeked', this.#onSeeked);
      this.#videoPlayer?.removeEventListener('ended', this.#onEnded);
      this.#videoPlayer?.removeEventListener('error', this.#onError);
      this.#videoPlayer?.audioTracks.removeEventListener('addtrack', this.#onAudioTrackAdded);
      this.#videoPlayer?.videoTracks.removeEventListener('addtrack', this.#onVideoTrackAdded);
      this.#videoPlayer?.textTracks.removeEventListener('addtrack', this.#onTextTrackAdded);
      this.#videoPlayer?.audioTracks.removeEventListener('removetrack', this.#onAudioTrackRemoved);
      this.#videoPlayer?.videoTracks.removeEventListener('removetrack', this.#onVideoTrackRemoved);
      this.#videoPlayer?.textTracks.removeEventListener('removetrack', this.#onTextTrackRemoved);
      (this.#videoPlayer as HTMLMediaElement)?.removeEventListener('waiting', this.#updateBufferedRanges);
      (this.#videoPlayer as HTMLMediaElement)?.removeEventListener('canplay', this.#updateBufferedRanges);
    };
    #onPlay: EventListener = (event?: Event) => {
      console.log(`[PlayerService] onPlay ${event?.type}`);
      this.updateStatus(this.#activeSessionId);
    }
    #onPause: EventListener = (event?: Event) => {
      console.log('[PlayerService] onPause');
      this.updateStatus(this.#activeSessionId);
    }
    #onSeeked: EventListener = (event?: Event) => {
      console.log('[PlayerService] onSeeked');
    }
    #onEnded: EventListener = (event?: Event) => {
      console.log('[PlayerService] onEnded');
      this.updateStatus(this.#activeSessionId);
      this.onUnload();
    }
    #onError: EventListener = (event?: Event) => {
      console.log(`[PlayerService] onError with code: {}, message: {}`,
        this.#videoPlayer?.error.code, this.#videoPlayer?.error.message);
      this.#hasError = true;
    };
    #onAudioTrackAdded: EventListener = (event?: Event) => {
      if (event instanceof TrackEvent) {
        let track = (event as TrackEvent).track;
        if (track !== undefined) {
          this.#playerServer?.addTrack({
            id: track.id,
            type: "AUDIO",
            kind: track.kind,
            label: track.label,
            language: track.language,
            enabled: (track as AudioTrack).enabled
          }, this.#activeSessionId);
          // Do other operations if needed.
        } else {
          console.error("Undefined audio track.");
        }
      }
    }
    #onVideoTrackAdded: EventListener = (event?: Event) => {
      if (event instanceof TrackEvent) {
        let track = (event as TrackEvent).track;
        if (track !== undefined) {
          this.#playerServer?.addTrack({
            id: track.id,
            type: "VIDEO",
            kind: track.kind,
            label: track.label,
            language: track.language,
            enabled: (track as VideoTrack).selected
          }, this.#activeSessionId);
          // Do other operations if needed.
        } else {
          console.error("Undefined video track.");
        }
      }
    }
    #onTextTrackAdded: EventListener = (event?: Event) => {
      if (event instanceof TrackEvent) {
        let track = (event as TrackEvent).track;
        if (track !== undefined) {
          this.#playerServer?.addTrack({
            id: track.id,
            type: "TEXT",
            kind: track.kind,
            label: track.label,
            language: track.language,
            mode: (track as TextTrack).mode
          }, this.#activeSessionId);
          // Do other operations if needed.
        } else {
          console.error("Undefined text track.");
        }
      }
    }
    #onAudioTrackRemoved: EventListener = (event?: Event) => {
      if (event instanceof TrackEvent) {
        let track = (event as TrackEvent).track;
        if (track !== undefined) {
          this.#playerServer?.removeTrack({
            id: track.id,
            type: "AUDIO",
            kind: track.kind,
            label: track.label,
            language: track.language,
            enabled: (track as AudioTrack).enabled
          }, this.#activeSessionId);
          // Do other operations if needed.
        } else {
          console.error("Undefined audio track.");
        }
      }
    }
    #onVideoTrackRemoved: EventListener = (event?: Event) => {
      if (event instanceof TrackEvent) {
        let track = (event as TrackEvent).track;
        if (track !== undefined) {
          this.#playerServer?.removeTrack({
            id: track.id,
            type: "VIDEO",
            kind: track.kind,
            label: track.label,
            language: track.language,
            enabled: (track as VideoTrack).selected
          }, this.#activeSessionId);
          // Do other operations if needed.
        } else {
          console.error("Undefined video track.");
        }
      }
    }
    #onTextTrackRemoved: EventListener = (event?: Event) => {
      if (event instanceof TrackEvent) {
        let track = (event as TrackEvent).track;
        if (track !== undefined) {
          this.#playerServer?.removeTrack({
            id: track.id,
            type: "TEXT",
            kind: track.kind,
            label: track.label,
            language: track.language,
            mode: (track as TextTrack).mode
          }, this.#activeSessionId);
          // Do other operations if needed.
        } else {
          console.error("Undefined text track.");
        }
      }
    }
    #updateBufferedRanges: EventListener = (event?: Event) => {
      const result: Array<ITimeRange> = [];
      const bufferedTimeRanges = this.#videoPlayer?.buffered;
      if (bufferedTimeRanges === undefined) {
        return;
      }
      for (let i = 0; i < bufferedTimeRanges.length; ++i) {
        result.push({
          start: bufferedTimeRanges.start(i),
          end: bufferedTimeRanges.end(i)
        });
      }
      this.#playerServer?.updateBufferedRanges(result, this.#activeSessionId);
    }
    #getPlaybackState = () : IPlayerSessionState => {
      if (!this.#videoPlayer) return IPlayerSessionState.ENDED;
      if (this.#hasError) return IPlayerSessionState.ERROR;
      if (this.#videoPlayer.ended) return IPlayerSessionState.ENDED;
      if (this.#videoPlayer.seeking) return IPlayerSessionState.SEEKING;
      if (this.#videoPlayer.paused) {
        return IPlayerSessionState.PAUSED;
      }
      return IPlayerSessionState.PLAYING;
    }
    updateStatus = (sessionId?: IPlayerSessionId) => {
      console.debug(`[PlayerService] updateStatus with sessionId: ${sessionId !== undefined ? sessionId?.id: "undefined"}`);
      const playerSessionStatus: IPlayerSessionStatus = {
        sessionId: sessionId,
        playbackState: this.#getPlaybackState(),
        playbackRate: this.#videoPlayer?.playbackRate || 1,
        isMuted: this.#videoPlayer?.muted || false,
        volume: this.#videoPlayer?.volume || 0,
        seekable: (this.#videoPlayer?.seekable.length || 0) > 0,
        duration: this.#videoPlayer?.duration
      }
      this.#playerServer?.updateStatus([playerSessionStatus]);
    }
    getCurrentPlaybackPosition = () : IPlayerSessionPosition => {
      return {
        sessionId: this.#activeSessionId,
        position: this.#videoPlayer?.currentTime ?? 0 as number,
      } as IPlayerSessionPosition;
    }
    onSurfaceViewCreated = (surfaceHandle: string): void => {
      console.log('[PlayerService] onSurfaceViewCreated called with surfaceHandle: ', surfaceHandle);
      this.#activeSurfaceHandle = surfaceHandle;
      this.#videoPlayer?.setSurfaceHandle(surfaceHandle);
    };
    onSurfaceViewDestroyed = (): void => {
      console.log('[PlayerService] onSurfaceViewDestroyed called');
      if (this.#activeSurfaceHandle !== undefined) {
        this.#videoPlayer?.clearSurfaceHandle(this.#activeSurfaceHandle);
      }
    }
    onCaptionViewCreated = (surfaceHandle: string): void => {
      console.log('[PlayerService] onCaptionViewCreated called with surfaceHandle: ', surfaceHandle);
      this.#activeCaptionHandle = surfaceHandle;
      this.#videoPlayer?.setCaptionViewHandle(surfaceHandle);
    };
    onCaptionViewDestroyed = (): void => {
      console.log('[PlayerService] onCaptionViewDestroyed called');
      if (this.#activeCaptionHandle !== undefined) {
        this.#videoPlayer?.clearCaptionViewHandle(this.#activeCaptionHandle);
      }
    }
    #findValueByKey = (httpHeaders: Array<IHttpHeader> | undefined, key: string) : string | undefined => {
      if (httpHeaders !== undefined) {
        for (let i : number = 0; i < httpHeaders.length; ++i) {
          if (httpHeaders[i].name === key) {
              return httpHeaders[i].value;
          }
        }
      }
      return undefined;
    }
    onLoad = async (urlInfo: IPlayerSessionMediaInfo,
        loadParams?: IPlayerSessionLoadParams,
        sessionId? : IPlayerSessionId): Promise<void> => {
      console.log(`[PlayerService] onLoad with urlInfo: {}`, JSON.stringify(urlInfo));
      if (loadParams !== undefined) {
        console.log(`[PlayerService] onLoad with loadParams: {}`, JSON.stringify(loadParams));
      }
      this.#hasError = false;
      const content = {
        "uri" : urlInfo.mediaUrl.url,
        "container": this.#findValueByKey(urlInfo.mediaUrl.httpHeaders, 'container') || 'FMP4'
      };
      this.onUnload();
      this.#activeSessionId = sessionId;
      await this.#initializeVideoPlayer();
      this.#msePlayer?.load(content, this.#AUTOPLAY);
    }
    onUnload = () => {
      console.log('[PlayerService] onUnload');
      this.#removeEventListeners();
      this.#msePlayer?.unload();
      this.#msePlayer = undefined;
      this.#videoPlayer?.deinitialize();
      this.#videoPlayer = undefined;
      this.#hasError = false;
      // @ts-ignore
      global.gmedia = null;
    };
    handlePlay = () => {
      console.log('[PlayerService] handlePlay');
      if (this.#videoPlayer) {
        this.#videoPlayer.play();
      } else {
        console.log('[PlayerService] handlePlay this.#videoPlayer is undefined');
      }
    };
    handlePause = () => {
      console.log('[PlayerService] handlePause');
      if (this.#videoPlayer) {
        this.#videoPlayer.pause();
      } else {
        console.log('[PlayerService] handlePause this.#videoPlayer is undefined');
      }
    };
    handleSeek = (seekTimeSeconds: number) => {
      console.log(`[PlayerService] handleSeek with seekTimeSeconds: ${seekTimeSeconds}`);
      if (this.#videoPlayer) {
        const currentTime = this.#videoPlayer.currentTime;
        this.#videoPlayer.currentTime = currentTime + seekTimeSeconds;
      } else {
        console.log('[PlayerService] handleSeek this.#videoPlayer is undefined');
      }
    }
    handleSetMute = (isMuted: boolean) => {
      console.debug('[PlayerService] handleSetMute');
      if (this.#videoPlayer) {
        this.#videoPlayer.muted = isMuted;
        this.updateStatus(this.#activeSessionId);
      } else {
        console.log('[PlayerService] handleSetMute this.#videoPlayer is undefined');
      }
    };
    handleSetVolume = (volume: number) => {
      console.debug('[PlayerService] handleSetVolume');
      if (this.#videoPlayer) {
        this.#videoPlayer.volume = volume;
        this.updateStatus(this.#activeSessionId);
      }
    }
    handleSetActiveTrack = (trackType: string, trackId: string) => {
      console.debug(`[PlayerService] handleSetActiveTrack ${trackType} ${trackId}`);
      if (trackType === 'AUDIO') {
        if (this.#msePlayer) {
          const audioTrackList = this.#msePlayer.getAudioLanguages();
          if (audioTrackList !== undefined && audioTrackList.length > 0) {
            let trackLang: string = audioTrackList[parseInt(trackId)];
            this.#msePlayer.selectAudioLanguage(trackLang);
          } else {
            console.error('[PlayerService] handleSetActiveTrack - audio track list is empty.');
          }
        } else {
          console.error('[PlayerService]: handleSetActiveTrack msePlayer not initialised');
        }
      } else {
        console.debug('[PlayerService] handleSetActiveTrack - only audio track handling is implemented.');
      }
    }
    async start(): Promise<void> {
      console.log('[PlayerService] start');
      this.#playerServerFactory = new PlayerServerFactory();
      this.#playerServer = this.#playerServerFactory.getOrMakeServer();
      this.#playerServerHandler = new PlayerServerHandler(this);
      if (this.#playerServerHandler !== undefined) {
        console.log('[PlayerService] Calling setHandler');
        this.#playerServer?.setHandler(
          this.#playerServerHandler,
          this.#serviceComponentId);
      }
      await this.#initializeVideoPlayer();
      console.log('[PlayerService] Service started');
    }
    async stop(): Promise<void> {
      this.onUnload();
    }
  }
  const playerService: PlayerService = new PlayerService();
  // Lifecycle methods
  export async function onStartService(): Promise<void> {
    console.log('[PlayerService] Called onStartService()');
    // set global.navigator and globa.window needed by @amazon-devices/react-native-w3cmedia
    // This is a temporary workaround till either @amazon-devices/react-native-w3cmedia or headless runtime
    // do this on behalf of the app developer
    // @ts-ignore
    let navigator = global.navigator;
    if (navigator === undefined) {
      // @ts-ignore
      global.navigator = navigator = {};
    }
    /**
     * Sets up global variables for React Native.
     * You can use this module directly, or just require InitializeCore.
     */
    // @ts-ignore
    if (global.window === undefined) {
      // $FlowExpectedError[cannot-write] The global isn't writable anywhere but here, where we define it.
      // @ts-ignore
      global.window = global;
    }
    // @ts-ignore
    if (global.self === undefined) {
      // $FlowExpectedError[cannot-write] The global isn't writable anywhere but here, where we define it.
      // @ts-ignore
      global.self = global;
    }
    await playerService.start();
    console.log('[PlayerService] Service started');
  }
  export async function onStopService(): Promise<void> {
    console.log('[PlayerService] Called onStopService()');
    await playerService.stop();
    console.log('[PlayerService] Service stopped');
  }