import { createElement } from "../../lib/skeleton/index.js";
import rxjs, { effect, onClick } from "../../lib/rx.js";
import { qs, safe } from "../../lib/dom.js";
import { onDestroy } from "../../lib/skeleton/lifecycle.js";
import { loadCSS, loadJS } from "../../helpers/loader.js";
import { settings_get, settings_put } from "../../lib/settings.js";
import Chromecast from "../../lib/chromecast.js";
import assert from "../../lib/assert.js";

import ctrlError from "../ctrl_error.js";
import { renderMenubar, buttonDownload } from "./component_menubar.js";

import { ICON } from "./common_icon.js";
import { formatTimecode } from "./common_player.js";
import { transition } from "./common.js";

const STATUS_PLAYING = "PLAYING";
const STATUS_PAUSED = "PAUSED";
const STATUS_BUFFERING = "BUFFERING";

export default function(render, { getFilename, getDownloadUrl }) {
    const $page = createElement(`
        <div class="component_audioplayer">
            <component-menubar filename="${safe(getFilename())}"></component-menubar>
            <div class="audioplayer_container">
                <div class="audioplayer_box">
                    <div data-bind="loader">
                        <div class="audioplayer_loader"></div>
                        <span class="percent"></span>
                        <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0nMTIwcHgnIGhlaWdodD0nMTIwcHgnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiBjbGFzcz0idWlsLXJpbmctYWx0Ij4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjNmY2ZjZmIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgZHVyPSIycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGZyb209IjAiIHRvPSI1MDIiPjwvYW5pbWF0ZT4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNoYXJyYXkiIGR1cj0iMnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB2YWx1ZXM9IjE1MC42IDEwMC40OzEgMjUwOzE1MC42IDEwMC40Ij48L2FuaW1hdGU+CiAgPC9jaXJjbGU+Cjwvc3ZnPgo=" alt="loading">
                    </div>
                    <div id="waveform"></div>
                    <div class="audioplayer_control hidden">
                        <div class="buttons no-select">
                            <span>
                                <img class="component_icon" draggable="false" src="${ICON.PLAY}" alt="play">
                                <img class="component_icon hidden" draggable="false" src="${ICON.PAUSE}" alt="pause">
                                <component-icon name="loading" class="hidden"></component-icon>
                            </span>
                            <span>
                                <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_MUTE}" alt="volume_mute">
                                <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_LOW}" alt="volume_low">
                                <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_NORMAL}" alt="volume">
                            </span>
                            <input type="range" min="0" max="100" value="52">
                        </div>
                        <div class="timecode">
                            <span id="currentTime">00:00</span>
                            <span id="separator" class="no-select">/</span>
                            <span id="totalDuration">02:13</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    `);
    render($page);
    renderMenubar(qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl()));

    transition(qs($page, ".audioplayer_box"));

    const $control = {
        main: qs($page, `.audioplayer_control`),

        play: qs($page, `.audioplayer_control [alt="play"]`),
        pause: qs($page, `.audioplayer_control [alt="pause"]`),
        loading: qs($page, `.audioplayer_control component-icon[name="loading"]`),
    };
    const $volume = {
        range: qs($page, `input[type="range"]`),
        icon_mute: qs($page, `img[alt="volume_mute"]`),
        icon_low: qs($page, `img[alt="volume_low"]`),
        icon_normal: qs($page, `img[alt="volume"]`),
    };
    const currentTime$ = new rxjs.BehaviorSubject([
        0, // starting time - does change when seeking to another point
        0, // offset to align the audio context currentTime. otherwise when seeking, the
        // currentTime keep growing and progress bar goes haywire
    ]);
    const currentTime = (wavesurfer) => {
        return currentTime$.value[0] + (wavesurfer.backend.ac.currentTime - currentTime$.value[1]);
    };
    const setVolume = (volume, wavesurfer) => {
        settings_put("volume", volume);
        wavesurfer.setVolume(volume / 100);
        $volume.range.value = volume;
        if (volume === 0) {
            $volume.icon_mute.classList.remove("hidden");
            $volume.icon_low.classList.add("hidden");
            $volume.icon_normal.classList.add("hidden");
        } else if (volume < 50) {
            $volume.icon_mute.classList.add("hidden");
            $volume.icon_low.classList.remove("hidden");
            $volume.icon_normal.classList.add("hidden");
        } else {
            $volume.icon_mute.classList.add("hidden");
            $volume.icon_low.classList.add("hidden");
            $volume.icon_normal.classList.remove("hidden");
        }
    };
    const setStatus = (status, wavesurfer) => {
        switch (status) {
        case STATUS_PLAYING:
            $control.play.classList.add("hidden");
            $control.pause.classList.remove("hidden");
            $control.loading.classList.add("hidden");
            wavesurfer.backend.ac.resume();
            break;
        case STATUS_PAUSED:
            $control.play.classList.remove("hidden");
            $control.pause.classList.add("hidden");
            $control.loading.classList.add("hidden");
            wavesurfer.backend.ac.suspend();
            break;
        case STATUS_BUFFERING:
            $control.play.classList.add("hidden");
            $control.pause.classList.add("hidden");
            $control.loading.classList.remove("hidden");
            break;
        default:
            assert.fail(status);
        }
    };
    const setSeek = (newTime, wavesurfer) => {
        currentTime$.next([newTime, wavesurfer.backend.ac.currentTime]);
        wavesurfer.backend.source.stop(0);
        wavesurfer.backend.disconnectSource();
        wavesurfer.backend.createSource();
        wavesurfer.backend.source.start(0, newTime);
    };

    // feature1: setup the dom
    const setup$ = rxjs.of(qs($page, "#waveform")).pipe(
        rxjs.mergeMap(($node) => Promise.resolve(window.WaveSurfer.create({
            container: $node,
            interact: false,

            waveColor: "#323639",
            progressColor: "#808080",
            cursorColor: "#6f6f6f",
            cursorWidth: 3,
            height: 200,
            barWidth: 1,
        }))),
        rxjs.tap((wavesurfer) => {
            wavesurfer.load(getDownloadUrl());
            wavesurfer.on("error", (err) => {
                throw new Error(err);
            });
            wavesurfer.on("ready", () => {
                $control.main.classList.remove("hidden");
                qs($control.main, "#totalDuration").textContent = formatTimecode(wavesurfer.getDuration());
            });
            onDestroy(() => wavesurfer.destroy());
        }),
        rxjs.catchError(ctrlError()),
        rxjs.shareReplay(1),
    );
    effect(setup$);

    // feature2: loading animation
    effect(setup$.pipe(
        rxjs.mergeMap((wavesurfer) => new rxjs.Observable((observer) => {
            wavesurfer.on("loading", (n) => observer.next(n));
            wavesurfer.on("ready", () => observer.next(100));
        })),
        rxjs.mergeMap((n) => {
            const $loader = qs($page, `[data-bind="loader"]`);
            $loader.querySelector(".audioplayer_loader").style.width = `${n}%`;
            $loader.querySelector(".percent").textContent = `${n}%`;
            if (n !== 100) return rxjs.EMPTY;
            return rxjs.of(null).pipe(
                rxjs.delay(200),
                rxjs.tap(() => $loader.classList.add("hidden")),
            );
        }),
    ));

    // feature3: connect the audio
    const ready$ = setup$.pipe(
        rxjs.mergeMap((wavesurfer) => new rxjs.Observable((observer) => {
            wavesurfer.on("ready", () => observer.next(wavesurfer));
        })),
        rxjs.share(),
    );
    effect(ready$.pipe(rxjs.tap((wavesurfer) => {
        wavesurfer.backend.createSource();
        wavesurfer.backend.source.start(0, 0);
        wavesurfer.backend.ac.suspend();
        currentTime$.next([0, wavesurfer.backend.ac.currentTime]);
    })));

    // feature4: hint of song progress
    effect(setup$.pipe(
        rxjs.mergeMap(() => rxjs.merge(
            onClick($control.play).pipe(rxjs.mapTo(STATUS_PLAYING)),
            rxjs.fromEvent(document, "keydown").pipe(rxjs.mapTo(STATUS_PLAYING)),
        )),
        rxjs.first(),
        rxjs.mergeMap((status) => setup$.pipe(rxjs.tap((wavesurfer) => setStatus(status, wavesurfer)))),
        rxjs.switchMap((wavesurfer) => rxjs.animationFrames().pipe(rxjs.tap(() => {
            const _currentTime = currentTime(wavesurfer);
            const percent = _currentTime / wavesurfer.getDuration();
            if (percent > 1) return;
            wavesurfer.drawer.progress(percent);
            qs($control.main, "#currentTime").textContent = formatTimecode(_currentTime);
        }))),
    ));

    // feature5: player control - play / pause
    effect(setup$.pipe(
        rxjs.mergeMap((wavesurfer) => rxjs.merge(
            onClick($control.play).pipe(rxjs.mapTo(STATUS_PLAYING)),
            onClick($control.pause).pipe(rxjs.mapTo(STATUS_PAUSED)),
        ).pipe(
            rxjs.tap((status) => setStatus(status, wavesurfer)),
        )),
    ));

    // feature6: player control - volume
    effect(setup$.pipe(
        rxjs.switchMap(() => rxjs.fromEvent($volume.range, "input").pipe(rxjs.map((e) => e.target.value))),
        rxjs.startWith(settings_get("volume") === null ? 80 : settings_get("volume")),
        rxjs.mergeMap((volume) => setup$.pipe(rxjs.tap((wavesurfer) => setVolume(parseInt(volume), wavesurfer)))),
    ));

    // feature7: player control - seek
    effect(ready$.pipe(
        rxjs.mergeMap((wavesurfer) => rxjs.fromEvent(qs($page, "#waveform"), "click").pipe(
            rxjs.map((e) => ({ e, wavesurfer })),
        )),
        rxjs.map(({ e, wavesurfer }) => {
            const rec = e.target.closest("#waveform").getBoundingClientRect();
            return { wavesurfer, progress: (e.clientX - rec.x) / rec.width };
        }),
        rxjs.tap(({ progress, wavesurfer }) => {
            wavesurfer.drawer.progress(progress);
            const newTime = wavesurfer.getDuration() * progress;
            setSeek(newTime, wavesurfer);
        }),
    ));

    // feature8: player control - keyboard shortcut
    effect(ready$.pipe(
        rxjs.switchMap((wavesurfer) => rxjs.fromEvent(document, "keydown").pipe(
            rxjs.map((e) => e.code),
            rxjs.tap((code) => {
                switch (code) {
                case "Space":
                case "KeyK":
                    setStatus(
                        wavesurfer.backend.ac.state === "suspended"
                            ? STATUS_PLAYING
                            : STATUS_PAUSED,
                        wavesurfer,
                    );
                    break;
                case "KeyM":
                    setVolume(wavesurfer.getVolume() > 0 ? 0 : settings_get("volume"), wavesurfer);
                    break;
                case "ArrowUp":
                    setVolume(Math.min(wavesurfer.getVolume()*100 + 10, 100), wavesurfer);
                    break;
                case "ArrowDown":
                    setVolume(Math.max(wavesurfer.getVolume()*100 - 10, 0), wavesurfer);
                    break;
                case "KeyL":
                    setSeek(Math.min(wavesurfer.getDuration(), currentTime(wavesurfer) + 10), wavesurfer);
                    break;
                case "KeyF":
                    // chromecastLoader();
                    break;
                case "KeyJ":
                    setSeek(Math.max(0, currentTime(wavesurfer) - 10), wavesurfer);
                    break;
                case "Digit0":
                    setSeek(0, wavesurfer);
                    break;
                case "Digit1":
                    setSeek(wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit2":
                    setSeek(2 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit3":
                    setSeek(3 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit4":
                    setSeek(4 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit5":
                    setSeek(5 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit6":
                    setSeek(6 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit7":
                    setSeek(7 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit8":
                    setSeek(8 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                case "Digit9":
                    setSeek(9 * wavesurfer.getDuration() / 10, wavesurfer);
                    break;
                }
            }),
        )),
    ));

    // // feature9: setup chromecast
    // effect(ready$.pipe(
    //     rxjs.tap(() => renderMenubar(buildMenubar(
    //         menubarChromecast(),
    //         menubarDownload(),
    //     ))),
    // ));
    // effect(rxjs.combineLatest(
    //     setup$,
    //     getSession(),
    //     getConfig(),
    // ).pipe(
    //     rxjs.mergeMap(async ([wavesurfer, user, config]) => {
    //         if (!Chromecast.isAvailable()) return;
    //         const filename = basename(decodeURIComponent(location.pathname));
    //         // const link = Chromecast.createLink(getDownloadUrl());
    //         const media = new chrome.cast.media.MediaInfo(
    //             getDownloadUrl(),
    //             mime,
    //         );
    //         media.metadata = new chrome.cast.media.MusicTrackMediaMetadata()
    //         media.metadata.title = "test";
    //         media.metadata.title = filename.substr(0, filename.lastIndexOf(extname(filename)));
    //         media.metadata.subtitle = config.name;
    //         media.metadata.albumName = config.name;
    //         media.metadata.images = [
    //             new chrome.cast.Image(origin + "/assets/icons/music.png"),
    //         ];
    //         wavesurfer.setMute(true);
    //         wavesurfer.pause();

    //         const session = Chromecast.session();
    //         if (!session) return
    //         setVolume(session.getVolume() * 100);

    //         const req = await Chromecast.createRequest(media, user.authorization);
    //         return session.loadMedia(req);
    //         // .catch((err) => {
    //         //     console.error(err);
    //         //     notify.send(t("Cannot establish a connection"), "error");
    //         //     setIsChromecast(false);
    //         //     setIsLoading(false);
    //         // });
    //     }),
    // ));
}

export function init() {
    return Promise.all([
        setup_chromecast(),
        loadJS(import.meta.url, "../../lib/vendor/wavesurfer.js"),
        loadCSS(import.meta.url, "./application_audio.css"),
    ]);
}

function setup_chromecast() {
    if (!("chrome" in window)) {
        return Promise.resolve();
    } else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
        return Promise.resolve();
    }
    // if (!CONFIG.enable_chromecast) {
    //     return Promise.resolve();
    // } else
    return Chromecast.init();
}
