import React from "react"
import classnames from "classnames"
import Marquee from "react-fast-marquee"

import Controls from "components/Player/Controls"

import Image from "components/Image"

import request from "comty.js/handlers/request"

import "./index.less"

function composeRgbValues(values) {
    let value = ""

    // only get the first 3 values
    for (let i = 0; i < 3; i++) {
        // if last value, don't add comma
        if (i === 2) {
            value += `${values[i]}`
            continue
        }

        value += `${values[i]}, `
    }

    return value
}

function calculateLineTime(line) {
    if (!line) {
        return 0
    }

    return line.endTimeMs - line.startTimeMs
}

function isOverflown(element) {
    if (!element) {
        return false
    }

    return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}

class PlayerController extends React.Component {
    state = {
        colorAnalysis: null,
        currentDragWidth: 0,
        titleOverflown: false,

        currentDuration: 0,
        currentTime: 0,

        currentPlaying: app.cores.player.getState("currentAudioManifest"),
        loading: app.cores.player.getState("loading") ?? false,
        playbackStatus: app.cores.player.getState("playbackStatus") ?? "stopped",

        audioMuted: app.cores.player.getState("audioMuted") ?? false,
        volume: app.cores.player.getState("audioVolume"),

        syncModeLocked: app.cores.player.getState("syncModeLocked"),
        syncMode: app.cores.player.getState("syncMode"),
    }

    events = {
        "player.coverColorAnalysis.update": (colorAnalysis) => {
            this.setState({ colorAnalysis })
        },
        "player.seek.update": (seekTime) => {
            this.setState({
                currentTime: seekTime,
            })
        },
        "player.status.update": (data) => {
            this.setState({ playbackStatus: data })
        },
        "player.current.update": (data) => {
            this.setState({ titleOverflown: false })

            this.setState({ currentPlaying: data })
        },
        "player.syncModeLocked.update": (to) => {
            this.setState({ syncModeLocked: to })
        },
        "player.syncMode.update": (to) => {
            this.setState({ syncMode: to })
        },
        "player.mute.update": (data) => {
            this.setState({ audioMuted: data })
        },
        "player.volume.update": (data) => {
            this.setState({ audioVolume: data })
        },
        "player.loading.update": (data) => {
            this.setState({ loading: data })
        },
    }

    titleRef = React.createRef()

    startSync() {
        // create a interval to get state from player
        if (this.syncInterval) {
            clearInterval(this.syncInterval)
        }

        this.syncInterval = setInterval(() => {
            const currentState = app.cores.player.currentState()

            this.setState({
                currentDuration: currentState.duration,
                currentTime: currentState.time,
                colorAnalysis: currentState.colorAnalysis,
            })

            const titleOverflown = isOverflown(this.titleRef.current)

            this.setState({ titleOverflown: titleOverflown })
        }, 800)
    }

    onClickPreviousButton = () => {
        app.cores.player.playback.previous()
    }

    onClickNextButton = () => {
        app.cores.player.playback.next()
    }

    onClickTooglePlayButton = () => {
        if (this.state?.playbackStatus === "playing") {
            app.cores.player.playback.pause()
        } else {
            app.cores.player.playback.play()
        }
    }

    updateVolume = (value) => {
        app.cores.player.volume(value)
    }

    toogleMute = () => {
        app.cores.player.toogleMute()
    }

    componentDidMount() {
        for (const event in this.events) {
            app.eventBus.on(event, this.events[event])
        }

        if (this.syncInterval) {
            clearInterval(this.syncInterval)
        }

        this.startSync()
    }

    componentWillUnmount() {
        for (const event in this.events) {
            app.eventBus.off(event, this.events[event])
        }

        if (this.syncInterval) {
            clearInterval(this.syncInterval)
        }
    }

    onDragEnd = (seekTime) => {
        this.setState({
            currentDragWidth: 0,
            dragging: false,
        })

        app.cores.player.seek(seekTime)
    }

    render() {
        //const bgColor = RGBStringToValues(getComputedStyle(document.documentElement).getPropertyValue("--background-color-accent-values"))

        return <div className="player_controller_wrapper">
            <div
                className={classnames(
                    "player_controller",
                )}
            >
                <div className="player_controller_cover">
                    <Image
                        src={this.state.currentPlaying?.cover ?? this.state.currentPlaying?.thumbnail ?? "/assets/no_song.png"}
                    />
                </div>

                <div className="player_controller_left">
                    <div className="player_controller_info">
                        <div className="player_controller_info_title">
                            {
                                <h4
                                    ref={this.titleRef}
                                    className={classnames(
                                        "player_controller_info_title_text",
                                        {
                                            ["overflown"]: this.state.titleOverflown,
                                        }
                                    )}
                                >
                                    {
                                        this.state.plabackState === "stopped" ? "Nothing is playing" : <>
                                            {this.state.currentPlaying?.title ?? "Nothing is playing"}
                                        </>
                                    }
                                </h4>
                            }

                            {this.state.titleOverflown &&
                                <Marquee
                                    //gradient
                                    //gradientColor={bgColor}
                                    //gradientWidth={20}
                                    play={this.state.plabackState !== "stopped"}
                                >
                                    <h4>
                                        {
                                            this.state.plabackState === "stopped" ? "Nothing is playing" : <>
                                                {this.state.currentPlaying?.title ?? "Nothing is playing"}
                                            </>
                                        }
                                    </h4>
                                </Marquee>}
                        </div>
                        <div className="player_controller_info_artist">
                            {
                                this.state.currentPlaying?.artist && <>
                                    <h3>
                                        {this.state.currentPlaying?.artist ?? "Unknown"}
                                    </h3>
                                    {
                                        this.state.currentPlaying?.album && <>
                                            <span> - </span>
                                            <h3>
                                                {this.state.currentPlaying?.album ?? "Unknown"}
                                            </h3>
                                        </>
                                    }
                                </>
                            }
                        </div>
                    </div>

                    <Controls
                        className="player_controller_controls"
                        controls={{
                            previous: this.onClickPreviousButton,
                            toogle: this.onClickTooglePlayButton,
                            next: this.onClickNextButton,
                        }}
                        syncModeLocked={this.state.syncModeLocked}
                        playbackStatus={this.state.playbackStatus}
                        loading={this.state.loading}
                        audioVolume={this.state.audioVolume}
                        audioMuted={this.state.audioMuted}
                        onVolumeUpdate={this.updateVolume}
                        onMuteUpdate={this.toogleMute}
                    />
                </div>

                <div className="player_controller_progress_wrapper">
                    <div
                        className="player_controller_progress"
                        onMouseDown={(e) => {
                            this.setState({
                                dragging: true,
                            })
                        }}
                        onMouseUp={(e) => {
                            const rect = e.currentTarget.getBoundingClientRect()
                            const seekTime = this.state.currentDuration * (e.clientX - rect.left) / rect.width

                            this.onDragEnd(seekTime)
                        }}
                        onMouseMove={(e) => {
                            const rect = e.currentTarget.getBoundingClientRect()
                            const atWidth = (e.clientX - rect.left) / rect.width * 100

                            this.setState({ currentDragWidth: atWidth })
                        }}
                    >
                        <div className="player_controller_progress_bar"
                            style={{
                                width: `${this.state.dragging ? this.state.currentDragWidth : this.state.currentTime / this.state.currentDuration * 100}%`
                            }}
                        />
                    </div>
                </div>
            </div>
        </div>
    }
}

export default class SyncLyrics extends React.Component {
    state = {
        loading: true,
        notAvailable: false,

        currentManifest: null,
        currentStatus: null,

        canvas_url: null,
        lyrics: null,
        currentLine: null,

        colorAnalysis: null,

        classnames: {
            "cinematic-mode": false,
            "centered-player": false,
            "video-canvas-enabled": false,
        }
    }

    visualizerRef = React.createRef()

    videoCanvasRef = React.createRef()

    coverCanvasRef = React.createRef()

    events = {
        "player.current.update": (currentManifest) => {
            this.setState({ currentManifest })

            if (document.startViewTransition) {
                document.startViewTransition(this.loadLyrics)
            } else {
                this.loadLyrics()
            }
        },
        "player.coverColorAnalysis.update": (colorAnalysis) => {
            this.setState({ colorAnalysis })
        },
        "player.status.update": (currentStatus) => {
            this.setState({ currentStatus })
        }
    }

    toogleClassName = (className, to) => {
        if (typeof to === "undefined") {
            to = !this.state.classnames[className]
        }

        if (to) {
            if (this.state.classnames[className] === true) {
                return false
            }

            //app.message.info("Toogling on " + className)

            this.setState({
                classnames: {
                    ...this.state.classnames,
                    [className]: true
                },
            })

            return true
        } else {
            if (this.state.classnames[className] === false) {
                return false
            }

            //app.message.info("Toogling off " + className)

            this.setState({
                classnames: {
                    ...this.state.classnames,
                    [className]: false
                },
            })

            return true
        }
    }

    toogleVideoCanvas = (to) => {
        return this.toogleClassName("video-canvas-enabled", to)
    }

    toogleCenteredControllerMode = (to) => {
        return this.toogleClassName("centered-player", to)
    }

    toogleCinematicMode = (to) => {
        return this.toogleClassName("cinematic-mode", to)
    }

    isCurrentLine = (line) => {
        if (!this.state.currentLine) {
            return false
        }

        return this.state.currentLine.startTimeMs === line.startTimeMs
    }

    loadLyrics = async () => {
        if (this.syncInterval) {
            clearInterval(this.syncInterval)
        }

        if (!this.state.currentManifest) {
            return false
        }

        this.setState({
            loading: true,
            notAvailable: false,
            lyrics: null,
            currentLine: null,
            canvas_url: null,
        })

        const api = app.cores.api.instance().instances.music

        let response = await request({
            instance: api,
            method: "get",
            url: `/lyrics/${this.state.currentManifest._id}`,
        }).catch((err) => {
            console.error(err)

            this.setState({
                notAvailable: true,
            })

            return {}
        })

        let data = response.data ?? {
            lines: [],
            syncType: null,
        }

        console.log(this.state.currentManifest)
        console.log(data)

        if (data.lines.length > 0 && data.syncType === "LINE_SYNCED") {
            data.lines = data.lines.map((line, index) => {
                const ref = React.createRef()

                line.ref = ref

                line.startTimeMs = Number(line.startTimeMs)

                const nextLine = data.lines[index + 1]

                // calculate end time
                line.endTimeMs = nextLine ? Number(nextLine.startTimeMs) : Math.floor(app.cores.player.duration() * 1000)

                return line
            })
        }

        if (data.canvas_url) {
            //app.message.info("Video canvas loaded")
            console.log(`[SyncLyrics] Video canvas loaded`)

            this.toogleVideoCanvas(true)
        } else {
            //app.message.info("No video canvas available for this song")
            console.log(`[SyncLyrics] No video canvas available for this song`)

            this.toogleVideoCanvas(false)
        }

        // if has no lyrics or are unsynced, toogle cinematic mode off and center controller
        if (data.lines.length === 0 || data.syncType !== "LINE_SYNCED") {
            //app.message.info("No lyrics available for this song")

            console.log(`[SyncLyrics] No lyrics available for this song, sync type [${data.syncType}]`)

            this.toogleCinematicMode(false)
            this.toogleCenteredControllerMode(true)
        } else {
            //app.message.info("Lyrics loaded, starting sync...")
            console.log(`[SyncLyrics] Starting sync with type [${data.syncType}]`)

            this.toogleCenteredControllerMode(false)
            this.startLyricsSync()
        }

        // transform times
        this.setState({
            loading: false,
            syncType: data.syncType,
            canvas_url: data.canvas_url ?? null,
            lyrics: data.lines,
        })
    }

    startLyricsSync = () => {
        // create interval to sync lyrics
        if (this.syncInterval) {
            clearInterval(this.syncInterval)
        }

        // scroll to top
        this.visualizerRef.current.scrollTop = 0

        this.syncInterval = setInterval(() => {
            if (!this.state.lyrics || !Array.isArray(this.state.lyrics) || this.state.lyrics.length === 0 || !this.state.lyrics[0]) {
                console.warn(`Clearing interval because lyrics is not found or lyrics is empty, probably because memory leak or unmounted component`)
                clearInterval(this.syncInterval)
                return false
            }

            const { time } = app.cores.player.currentState()

            // transform audio seek time to lyrics time (ms from start) // remove decimals
            const transformedTime = Math.floor(time * 1000)

            const hasStartedFirst = transformedTime >= this.state.lyrics[0].startTimeMs

            if (!hasStartedFirst) {
                if (this.state.canvas_url) {
                    this.toogleCinematicMode(true)
                }

                return false
            }

            // find the closest line to the transformed time
            const line = this.state.lyrics.find((line) => {
                // match the closest line to the transformed time
                return transformedTime >= line.startTimeMs && transformedTime <= line.endTimeMs
            })

            if (!line || !line.ref) {
                console.warn(`Clearing interval because cannot find line to sync or line REF is not found, probably because memory leak or unmounted component`)
                clearInterval(this.syncInterval)

                return false
            }

            if (line) {
                if (this.isCurrentLine(line)) {
                    return false
                }

                // set current line
                this.setState({
                    currentLine: line,
                })

                //console.log(line)

                if (!line.ref.current) {
                    console.log(line)
                    console.warn(`Clearing interval because line CURRENT ref is not found, probably because memory leak or unmounted component`)
                    clearInterval(this.syncInterval)

                    return false
                }

                this.visualizerRef.current.scrollTo({
                    top: line.ref.current.offsetTop - (this.visualizerRef.current.offsetHeight / 2),
                    behavior: "smooth",
                })

                if (this.state.canvas_url) {
                    if (line.words === "♪" || line.words === "♫" || line.words === " " || line.words === "") {
                        //console.log(`[SyncLyrics] Toogling cinematic mode on because line is empty`)

                        this.toogleCinematicMode(true)
                    } else {
                        //console.log(`[SyncLyrics] Toogling cinematic mode off because line is not empty`)

                        this.toogleCinematicMode(false)
                    }
                } else {
                    if (this.state.classnames["cinematic-mode"] === true) {
                        this.toogleCinematicMode(false)
                    }
                }
            }
        }, 100)
    }

    componentDidMount = async () => {
        // register player events 
        for (const [event, callback] of Object.entries(this.events)) {
            app.eventBus.on(event, callback)
        }

        // get current playback status and time
        const {
            manifest,
            playbackStatus,
            colorAnalysis,
        } = app.cores.player.currentState()

        await this.setState({
            currentManifest: manifest,
            currentStatus: playbackStatus,
            colorAnalysis,
        })

        app.SidebarController.toggleVisibility(false)
        app.cores.style.compactMode(true)
        app.cores.style.applyVariant("dark")
        app.layout.floatingStack.toogleGlobalVisibility(false)

        // request full screen to browser
        if (document.fullscreenEnabled) {
            document.documentElement.requestFullscreen()
        }

        // listen when user exit full screen to exit cinematic mode
        document.addEventListener("fullscreenchange", () => {
            if (!document.fullscreenElement) {
                app.backLocation()
            }
        })

        window._hacks = {
            toogleVideoCanvas: this.toogleVideoCanvas,
            toogleCinematicMode: this.toogleCinematicMode,
            toogleCenteredControllerMode: this.toogleCenteredControllerMode,
        }

        await this.loadLyrics()
    }

    componentWillUnmount() {
        // unregister player events
        for (const [event, callback] of Object.entries(this.events)) {
            app.eventBus.off(event, callback)
        }

        // clear sync interval
        if (this.syncInterval) {
            clearInterval(this.syncInterval)
        }

        delete window._hacks

        app.SidebarController.toggleVisibility(true)
        app.cores.style.compactMode(false)
        app.cores.style.applyInitialVariant()
        app.layout.floatingStack.toogleGlobalVisibility(true)

        // exit full screen
        if (document.fullscreenEnabled) {
            document.exitFullscreen()
        }
    }

    renderLines() {
        if (!this.state.lyrics || this.state.notAvailable || this.state.syncType !== "LINE_SYNCED") {
            return null
        }

        return this.state.lyrics.map((line, index) => {
            return <div
                ref={line.ref}
                className={classnames(
                    "lyrics_viewer_lines_line",
                    {
                        ["current"]: this.isCurrentLine(line)
                    }
                )}
                id={line.startTimeMs}
                key={index}
            >
                <h2>
                    {line.words}
                </h2>
            </div>
        })
    }

    render() {
        return <div
            ref={this.visualizerRef}
            className={classnames(
                "lyrics_viewer",
                {
                    ["text_dark"]: this.state.colorAnalysis?.isDark ?? false,
                    ...Object.entries(this.state.classnames).reduce((acc, [key, value]) => {
                        return {
                            ...acc,
                            [key]: value,
                        }
                    }, {}),
                },
            )}
            style={{
                "--predominant-color": this.state.colorAnalysis?.hex ?? "unset",
                "--predominant-color-rgb-values": this.state.colorAnalysis?.value ? composeRgbValues(this.state.colorAnalysis?.value) : [0, 0, 0],
                "--line-time": `${calculateLineTime(this.state.currentLine)}ms`,
                "--line-animation-play-state": this.state.currentStatus === "playing" ? "running" : "paused",
            }}
        >

            <div
                className="lyrics_viewer_mask"
            />

            <div
                className="lyrics_viewer_video_canvas"
            >
                <video
                    src={this.state.canvas_url}
                    autoPlay
                    loop
                    muted
                    controls={false}
                    ref={this.videoCanvasRef}
                />
            </div>

            <div
                className="lyrics_viewer_cover"
            >
                <Image
                    src={this.state.currentManifest?.cover ?? this.state.currentManifest?.thumbnail ?? "/assets/no_song.png"}
                    ref={this.coverRef}
                />
            </div>

            <PlayerController />

            <div className="lyrics_viewer_content">
                <div className="lyrics_viewer_lines">
                    {
                        this.renderLines()
                    }
                </div>
            </div>
        </div>
    }
}