// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { CoreUtils } from '@services/utils/utils';
import { ElementController } from './ElementController';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreMedia } from '@singletons/media';
import { AddonFilterMediaPluginVideoJS, VIDEO_JS_PLAYER_CREATED } from '@addons/filter/mediaplugin/services/videojs';
import type { VideoJSPlayer } from 'video.js';

/**
 * Wrapper class to control the interactivity of a media element.
 */
export class MediaElementController extends ElementController {

    private media: HTMLMediaElement;
    private autoplay: boolean;
    private playing?: boolean;
    private playListener?: () => void;
    private pauseListener?: () => void;
    private jsPlayer = new CorePromisedValue<VideoJSPlayer | null>();
    private jsPlayerListener?: CoreEventObserver;
    private shouldEnable = false;
    private shouldDisable = false;

    constructor(media: HTMLMediaElement, enabled: boolean) {
        super(enabled);

        this.media = media;
        this.autoplay = media.autoplay;

        media.autoplay = false;

        this.initJSPlayer(media);

        enabled && this.onEnabled();
    }

    /**
     * @inheritdoc
     */
    async onEnabled(): Promise<void> {
        this.shouldEnable = true;
        this.shouldDisable = false;

        const jsPlayer = await this.jsPlayer;

        if (!this.shouldEnable || this.destroyed) {
            return;
        }

        const ready = this.playing ?? this.autoplay
            ? (jsPlayer ?? this.media).play()
            : Promise.resolve();

        try {
            await ready;

            this.addPlaybackEventListeners(jsPlayer);
        } catch (error) {
            CoreUtils.logUnhandledError('Error enabling media element', error);
        }
    }

    /**
     * @inheritdoc
     */
    async onDisabled(): Promise<void> {
        this.shouldDisable = true;
        this.shouldEnable = false;

        const jsPlayer = await this.jsPlayer;

        if (!this.shouldDisable || this.destroyed) {
            return;
        }

        this.removePlaybackEventListeners(jsPlayer);

        (jsPlayer ?? this.media).pause();
    }

    /**
     * @inheritdoc
     */
    async onDestroy(): Promise<void> {
        const jsPlayer = await this.jsPlayer;

        this.removePlaybackEventListeners(jsPlayer);
        jsPlayer?.dispose();
    }

    /**
     * Init JS Player instance.
     *
     * @param media Media element.
     */
    private async initJSPlayer(media: HTMLMediaElement): Promise<void> {
        if (!CoreMedia.mediaUsesJavascriptPlayer(media)) {
            this.jsPlayer.resolve(null);

            return;
        }

        const player = await this.searchJSPlayer();

        if (!player) {
            this.jsPlayerListener = CoreEvents.on(VIDEO_JS_PLAYER_CREATED, ({ element, player }) => {
                if (element !== media) {
                    return;
                }

                this.jsPlayerListener?.off();
                this.jsPlayer.resolve(player);
            });

            return;
        }

        this.jsPlayer.resolve(player);
    }

    /**
     * Start listening playback events.
     *
     * @param jsPlayer Javascript player instance (if any).
     */
    private addPlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void {
        if (jsPlayer) {
            jsPlayer.on('play', this.playListener = () => this.playing = true);
            jsPlayer.on('pause', this.pauseListener = () => this.playing = false);
        } else {
            this.media.addEventListener('play', this.playListener = () => this.playing = true);
            this.media.addEventListener('pause', this.pauseListener = () => this.playing = false);
        }
    }

    /**
     * Stop listening playback events.
     *
     * @param jsPlayer Javascript player instance (if any).
     */
    private removePlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void {
        if (jsPlayer) {
            this.playListener && jsPlayer.off('play', this.playListener);
            this.pauseListener && jsPlayer.off('pause', this.pauseListener);
        } else {
            this.playListener && this.media.removeEventListener('play', this.playListener);
            this.pauseListener && this.media.removeEventListener('pause', this.pauseListener);
        }

        delete this.playListener;
        delete this.pauseListener;
    }

    /**
     * Search JS player instance.
     *
     * @returns Player instance if found.
     */
    private async searchJSPlayer(): Promise<VideoJSPlayer | null> {
        return AddonFilterMediaPluginVideoJS.findPlayer(this.media.id)
            ?? AddonFilterMediaPluginVideoJS.findPlayer(this.media.id.replace('_html5_api', ''));
    }

}
