import "./css/media.scss";
import { createElement } from "../functions";
import { createIcon } from "./icon";
import { get } from "../utility/request";

export function createPicture(url) {
    return createElement('a', a => {
        a.className = 'ui-media-picture';
        a.target = '_blank';
        a.href = url;
    },
        createElement('img', img => {
            img.src = url;
        })
    );
}

function readBlob(blob) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = e => {
            const data = new Uint8Array(e.target.result);
            resolve(data);
        };
        reader.onerror = reject;
        reader.readAsArrayBuffer(blob);
    });
}

function playAmrArray(array) {
    return new Promise((resolve, reject) => {
        const samples = AMR.decode(array);
        if (samples != null) {
            resolve(samples);
        } else {
            reject();
        }
    });
}

function playPcm(samples, ended) {
    return new Promise(resolve => {
        const ctx = new AudioContext();
        ctx.addEventListener('statechange', () => resolve(ctx));
        const source = ctx.createBufferSource();
        if (typeof ended === 'function') {
            source.addEventListener('ended', () => ended(ctx));
        }
        const buffer = ctx.createBuffer(1, samples.length, 8000);
        if (typeof buffer.copyToChannel === 'function') {
            buffer.copyToChannel(samples, 0, 0);
        } else {
            const channelBuffer = buffer.getChannelData(0);
            channelBuffer.set(samples);
        }
        source.buffer = buffer;
        source.connect(ctx.destination);
        ctx.duration = buffer.duration;
        source.start();
        // resolve(ctx);
    });
}

function getTimeLabel(time) {
    // time = Math.round(time);
    // return String(Math.floor(time / 60)).padStart(2, '0') + ':' + String(time % 60).padStart(2, '0');
    if (isNaN(time) || time < 0) {
        return '0:00';
    }
    time = Math.floor(time);
    const m = Math.floor(time / 60);
    const h = Math.floor(m / 60);
    if (h > 0) {
        return h + ':' + String(m % 60).padStart(2, '0') + ':' + String(time % 60).padStart(2, '0');
    }
    return m + ':' + String(time % 60).padStart(2, '0');
}

export function createAudio(mime, url) {
    if ((mime === 'audio/amr' || mime === '.amr') && typeof AMR !== 'undefined') {
        const timestamp = createElement('span', 'ui-media-timestamp');
        timestamp.textContent = '0:00 / 0:00';
        let context;
        let timer;
        return createElement('div', 'ui-media-audio',
            createElement('button', button => {
                button.className = 'play';
                button.addEventListener('click', () => {
                    if (context != null) {
                        clearInterval(timer);
                        context.close();
                        context = null;
                        timestamp.textContent = '0:00 / 0:00';
                        button.className = 'play';
                        button.replaceChildren(createIcon('fa-solid', 'play'));
                        return;
                    }
                    get(url, { accept: mime })
                        .then(r => r.blob())
                        .then(r => readBlob(r))
                        .then(r => playAmrArray(r))
                        .then(r => playPcm(r, ctx => {
                            context = null;
                            clearInterval(timer);
                            timestamp.textContent = '0:00 / ' + getTimeLabel(ctx.duration);
                            button.className = 'play';
                            button.replaceChildren(createIcon('fa-solid', 'play'));
                        }))
                        .then(ctx => {
                            context = ctx;
                            button.className = 'stop';
                            button.replaceChildren(createIcon('fa-solid', 'stop'));
                            const total = getTimeLabel(ctx.duration);
                            const refresh = () => timestamp.textContent = getTimeLabel(ctx.currentTime) + ' / ' + total;
                            refresh();
                            timer = setInterval(refresh, 500);
                        })
                        .catch(e => {
                            clearInterval(timer);
                            console.error(e);
                        });
                });
            },
                createIcon('fa-solid', 'play')
            ),
            timestamp
        );
    }
    return createElement('audio', audio => {
        audio.src = url;
        audio.controls = true;
    });
}

export function createVideo(url) {
    return createElement('video', video => {
        video.className = 'ui-media-video';
        video.src = url;
        video.controls = true;
    });
}

export function createFile(url, icon = 'file-alt') {
    return createElement('div', `ui-media-file ${icon}`,
        createIcon('fa-solid', icon),
        createElement('a', a => {
            a.target = '_blank';
            a.href = url;
            a.innerText = 'Click here to view the file';
        })
    );
}

/**
 * 创建联动视频元素
 * @param {string[]} urls - 视频 url 数组
 * @param {any} [options] - 播放参数
 * @param {boolean} options.[autoPlay] - 是否自动播放
 * @param {boolean} options.[autoFullScreen] - 是否自动全屏
 * @param {boolean} options.[autoLoop] - 是否循环播放
 * @param {Function} options.[onLoaded] - 视频加载完成回调
 * @param {Function} [callback] - 视频元素处理回调函数
 * @returns {HTMLDivElement} 返回联动视频元素
 */
export function createVideoList(urls, options, callback) {
    if (!Array.isArray(urls)) {
        urls = [urls];
    }
    const length = urls.length;
    const container = createElement('div', 'ui-media-video-container');
    let seekBufferBar;
    let seekProgressBar;
    let playIcon;
    let timeLabel;
    let volumeProgressBar;
    let volumeIcon;

    const videos = Array(length);
    const waiting = Array(length);
    let prepared = 0;
    let ended = 0;

    let playing;
    let duration = -1;
    let durationLabel;
    let seekBarWidth;
    let mousing;
    const controller = createElement('div', 'ui-video-control active',
        createElement('div', seek => {
            seek.className = 'ui-video-bar seek-bar';
            seek.addEventListener('mousedown', e => {
                if (prepared < length || waiting.find(p => p) != null) {
                    return;
                }
                const width = seekBarWidth = seek.offsetWidth;
                const currentTime = Math.min(Math.max(e.offsetX * duration / width, 0), duration);
                videos.forEach(video => typeof video.fastSeek === 'function' ?
                    video.fastSeek(currentTime) :
                    (video.currentTime = currentTime));
                mousing = true;
                e.stopPropagation();
            });
            seek.addEventListener('mousemove', e => {
                if (mousing) {
                    if (!e.buttons) {
                        mousing = false;
                    } else {
                        const v = Math.min(Math.max(e.offsetX / seekBarWidth, 0), 1);
                        seekProgressBar.style.width = `${(v * 100).toFixed(2)}%`;
                    }
                }
            });
            seek.addEventListener('mouseup', e => {
                if (mousing) {
                    mousing = false;
                    const currentTime = Math.min(Math.max(e.offsetX * duration / seekBarWidth, 0), duration);
                    videos.forEach(video => typeof video.fastSeek === 'function' ?
                        video.fastSeek(currentTime) :
                        (video.currentTime = currentTime));
                }
            });
        },
            createElement('div', 'ui-video-duration seek-duration'),
            seekBufferBar = createElement('div', 'seek-buffers'),
            seekProgressBar = createElement('div', 'ui-video-progress seek-progress')
        ),
        playIcon = createElement('div', 'ui-video-icon play-icon',
            createIcon('fa-solid', 'play'),
            createIcon('fa-solid', 'pause')
        ),
        timeLabel = createElement('div', 'ui-video-time-label'),
        createElement('div', 'ui-video-volume-container',
            createElement('div', volume => {
                volume.className = 'ui-video-bar volume-bar';
                volume.addEventListener('mousedown', e => {
                    const v = Math.min(Math.max(e.offsetX / 60, 0), 1);
                    const video = videos[0];
                    if (video != null) {
                        video.volume = v;
                    }
                    volumeIcon.classList[v === 0 ? 'add' : 'remove']('muted');
                    mousing = true;
                    e.stopPropagation();
                });
                volume.addEventListener('mousemove', e => {
                    if (mousing) {
                        if (!e.buttons) {
                            mousing = false;
                        } else {
                            const v = Math.min(Math.max(e.offsetX / 60, 0), 1);
                            volumeProgressBar.style.width = `${(v * 100).toFixed(2)}%`;
                            const video = videos[0];
                            if (video != null) {
                                video.volume = v;
                            }
                            volumeIcon.classList[v === 0 ? 'add' : 'remove']('muted');
                        }
                    }
                });
                volume.addEventListener('mouseup', e => {
                    if (mousing) {
                        mousing = false;
                        const v = Math.min(Math.max(e.offsetX / 60, 0), 1);
                        const video = videos[0];
                        if (video != null) {
                            video.volume = v;
                        }
                        volumeIcon.classList[v === 0 ? 'add' : 'remove']('muted');
                    }
                });
            },
                createElement('div', 'ui-video-duration video-duration'),
                volumeProgressBar = createElement('div', 'ui-video-progress video-progress')
            ),
            volumeIcon = createElement('div', 'ui-video-icon volume-icon',
                createIcon('fa-solid', 'volume'),
                createIcon('fa-solid', 'volume-mute')
            )
        )
    );
    controller.addEventListener('mousedown', () => {
        if (prepared < length || waiting.find(p => p) != null) {
            // not prepared.
            return;
        }
        if (playing) {
            videos.forEach(video => video.pause());
        } else {
            ended = 0;
            videos.forEach(video => video.play().catch(() => { }));
        }
    });
    volumeIcon.addEventListener('mousedown', e => {
        if (volumeIcon.classList.contains('disabled')) {
            return;
        }
        const video = videos[0];
        if (video == null) {
            return;
        }
        if (volumeIcon.classList.contains('muted')) {
            video.volume = 1;
            volumeIcon.classList.remove('muted');
        } else {
            video.volume = 0;
            volumeIcon.classList.add('muted');
        }
        e.stopPropagation();
    });
    if (length === 1) {
        controller.append(createElement('div', icon => {
            icon.className = 'ui-video-icon fullscreen-icon';
            icon.addEventListener('mousedown', e => {
                if (prepared < length) {
                    e.stopPropagation();
                    return;
                }
                const video = videos[0];
                if (video != null) {
                    if (document.fullscreenElement == null) {
                        video.requestFullscreen().catch(() => { });
                    } else if (typeof document.exitFullscreen === 'function') {
                        document.exitFullscreen();
                    }
                }
            });
        },
            createIcon('fa-regular', 'expand')
        ));
    } else {
        controller.classList.add('no-fullscreen');
    }

    const content = createElement('div', 'ui-video-content');
    container.append(content, controller);

    urls.forEach((url, i) => {
        const video = createElement('video');
        videos[i] = video;
        video.src = url;
        video.addEventListener('durationchange', () => {
            const d = video.duration;
            if (d > duration) {
                duration = d;
                durationLabel = getTimeLabel(d);
                timeLabel.innerText = '0:00 / ' + durationLabel;
            }
        });
        video.addEventListener('loadeddata', () => {
            if (i === 0) {
                volumeProgressBar.style.width = `${(video.volume * 100).toFixed(2)}%`;
            } else {
                video.volume = 0;
            }
            prepared += 1;
            if (prepared >= length) {
                if (options?.autoPlay) {
                    // auto play
                    videos.forEach(v => v.play().catch(() => { }));
                    if (options?.autoFullScreen && length === 1 && document.fullscreenElement == null) {
                        video.requestFullscreen().catch(() => { });
                    }
                }
                if (typeof options?.onLoaded === 'function') {
                    options.onLoaded();
                }
            }
        });
        video.addEventListener('progress', () => {
            const buffered = video.buffered;
            for (let i = 0; i < buffered.length; ++i) {
                let buffer = seekBufferBar.children[i];
                if (buffer == null) {
                    seekBufferBar.append(buffer = createElement('div', 'ui-video-buffer'));
                }
                const start = buffered.start(i) * 100 / duration;
                const end = buffered.end(i) * 100 / duration;
                buffer.style.left = `${start.toFixed(2)}%`;
                buffer.style.width = `${(end - start).toFixed(2)}%`;
            }
            for (let i = seekBufferBar.children.length - 1; i >= buffered.length; i -= 1) {
                seekBufferBar.children[i].remove();
            }
        });
        video.addEventListener('canplaythrough', () => {
            const w = video.parentElement.querySelector('.ui-video-waiting');
            if (w != null) {
                w.style.opacity = 0;
            }
            if (waiting[i]) {
                waiting[i] = false;
                if (waiting.find(p => p) == null) {
                    videos.forEach(v => v.play().catch(() => { }));
                }
            }
        });
        video.addEventListener('pause', () => {
            playing = false;
            controller.classList.add('active');
            playIcon.classList.remove('pause');
        });
        video.addEventListener('playing', () => {
            playing = true;
            controller.classList.remove('active');
            playIcon.classList.add('pause');
        });
        video.addEventListener('waiting', () => {
            waiting[i] = true;
            const w = video.parentElement.querySelector('.ui-video-waiting');
            if (w != null) {
                w.style.opacity = 1;
            }
            videos.forEach(v => v.pause());
        });
        video.addEventListener('ended', () => {
            ended += 1;
            if (ended >= length) {
                if (options?.autoLoop) {
                    videos.forEach(v => v.play().catch(() => { }));
                } else {
                    playing = false;
                    controller.classList.add('active');
                    seekProgressBar.style.width = '0';
                    playIcon.classList.remove('pause');
                    timeLabel.innerText = '0:00 / ' + durationLabel;
                }
            }
        });
        if (i === 0) {
            video.addEventListener('timeupdate', () => {
                seekProgressBar.style.width = `${(video.currentTime * 100 / duration).toFixed(2)}%`;
                timeLabel.innerText = getTimeLabel(video.currentTime) + ' / ' + durationLabel;
            });
            video.addEventListener('volumechange', () => {
                volumeProgressBar.style.width = `${(video.volume * 100).toFixed(2)}%`;
            });
        }
        const wrapper = createElement('div', 'ui-video-wrapper',
            video,
            createElement('div', 'ui-video-waiting',
                createIcon('fa-regular', 'spinner-third')
            )
        );
        if (typeof callback === 'function') {
            callback(wrapper);
        }
        content.append(wrapper);
    });

    return container;
}