import React, { useContext, useEffect, useRef, useState } from 'react'
import { StaticQuery, graphql } from 'gatsby'
import Marked from 'reveal.js/plugin/markdown/marked.js'
import Prism from 'prismjs'
import classNames from 'classnames'

import { ChapterContext, LocaleContext } from '../context'
import '../styles/reveal.css'
import '../styles/plyr.css'
import classes from '../styles/slides.module.sass'

const CODE_LANGS = ['python', 'ini', 'bash']

function getFiles({ allMarkdownRemark }, lang) {
    return Object.assign(
        {},
        ...allMarkdownRemark.edges
            .filter(({ node }) => node.fields.lang === lang)
            .map(({ node }) => ({
                [node.fields.slug.replace('/', '')]: node.rawMarkdownBody,
            }))
    )
}

function getSlideContent(data, source, lang) {
    const files = getFiles(data, lang)
    const key = `${lang}/slides/${source}`
    const file = files[key] || ''
    return file.split('\n---\n').map(f => f.trim())
}

function timestampToSeconds(ts) {
    if (ts === null) return ts
    const [mins, secs] = ts.split(':')
    const seconds =
        secs.length > 2 ? parseFloat(`${secs.slice(0, 2)}.${secs.slice(2)}`) : parseInt(secs)
    return parseInt(mins) * 60 + seconds
}

const Slides = ({ source, start = null, end = null }) => {
    const { slideType, setSlideType } = useContext(ChapterContext)
    const { video, uiText } = useContext(LocaleContext)
    const hasVideo = video && start !== null && end !== null
    return hasVideo ? (
        <>
            <menu className={classes.menu}>
                {['video', 'slides'].map(value => {
                    const isActive = slideType === value
                    const tabClassNames = classNames(classes.tab, {
                        [classes.tabActive]: isActive,
                    })
                    return (
                        <label key={value} htmlFor={value} className={tabClassNames}>
                            <input
                                className={classes.radio}
                                name="type"
                                id={value}
                                type="radio"
                                defaultChecked={isActive}
                                onChange={() => setSlideType(value)}
                            />
                            {uiText[value]}
                        </label>
                    )
                })}
            </menu>
            {slideType === 'video' ? (
                <Video id={video} start={timestampToSeconds(start)} end={timestampToSeconds(end)} />
            ) : (
                <SlideDeck source={source} />
            )}
        </>
    ) : (
        <SlideDeck source={source} />
    )
}

const Video = ({ id, start = 0, end = 0 }) => {
    const ref = useRef(null)
    const { lang } = useContext(ChapterContext)
    const { uiText } = useContext(LocaleContext)
    const [duration, setDuration] = useState(0)
    const url = `https://www.youtube.com/embed/${id}?start=${start}&end=${end}&version=3&color=white&hl=${lang}&modestbranding=1&rel=0`
    const options = {
        duration: end,
        tooltips: { seek: false },
        disableContextMenu: false,
    }

    useEffect(() => {
        let player = null
        import('plyr').then(({ default: Plyr }) => {
            player = new Plyr(ref.current, options)
            player.on('ready', () => {
                if (ref.current) {
                    const marker = document.createElement('span')
                    ref.current.querySelector('.plyr__progress').appendChild(marker)
                    marker.className = 'plyr__tooltip plyr__marker'
                    marker.textContent = uiText.start
                    marker.addEventListener('click', () => {
                        player.currentTime = start
                        player.play()
                    })
                }
                setDuration(player.duration)
                player.currentTime = start
            })
            player.on('timeupdate', () => {
                if (player.currentTime > end) {
                    player.pause()
                    player.currentTime = end
                }
            })
            player.on('play', () => {
                if (player.currentTime >= end) {
                    player.currentTime = start
                }
            })
        })
        return () => {
            if (player) player.destroy()
        }
    }, []) // eslint-disable-line

    return (
        <div style={{ '--plyr-marker': `${(100 / duration) * start}%` }}>
            <div ref={ref}>
                <iframe
                    title={id}
                    width="800"
                    height="450"
                    src={url}
                    frameBorder="0"
                    allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
                    allowFullScreen
                />
            </div>
        </div>
    )
}

class SlideDeck extends React.Component {
    componentDidMount() {
        import('reveal.js').then(({ default: Reveal }) => {
            window.Reveal = Reveal
            window.marked = Marked
            import('reveal.js/plugin/markdown/markdown.js').then(({ RevealMarkdown }) => {
                RevealMarkdown.init()
                Reveal.initialize({
                    center: false,
                    progress: false,
                    showNotes: true,
                    controls: true,
                    width: '100%',
                    height: 600,
                    minScale: 0.75,
                    maxScale: 1,
                })
            })
        })
        Promise.all(CODE_LANGS.map(lang => import(`prismjs/components/prism-${lang}`))).then(() =>
            setTimeout(() => {
                Prism.highlightAll()
            }, 1000)
        )
    }

    componentWillUnmount() {
        // Work around default reveal.js behaviour that doesn't allow
        // re-initialization and clashes with React
        delete window.Reveal
        delete window.marked
        delete require.cache[require.resolve('reveal.js')]
        delete require.cache[require.resolve('reveal.js/plugin/markdown/markdown.js')]
    }

    render() {
        const { source } = this.props
        const revealClassNames = classNames('reveal', 'show-notes', classes.reveal)
        const slideClassNames = classNames('slides', classes.slides)

        return (
            <div className={classes.root}>
                <div className={revealClassNames}>
                    <ChapterContext.Consumer>
                        {({ lang }) => (
                            <StaticQuery
                                query={graphql`
                                    {
                                        allMarkdownRemark(
                                            filter: { frontmatter: { type: { eq: "slides" } } }
                                        ) {
                                            edges {
                                                node {
                                                    rawMarkdownBody
                                                    fields {
                                                        slug
                                                        lang
                                                    }
                                                }
                                            }
                                        }
                                    }
                                `}
                                render={data => {
                                    const content = getSlideContent(data, source, lang)
                                    return (
                                        <div className={slideClassNames}>
                                            {content.map((markdown, i) => (
                                                <section
                                                    key={i}
                                                    data-markdown=""
                                                    data-separator-notes="^Notes:"
                                                >
                                                    <textarea
                                                        data-template
                                                        defaultValue={markdown}
                                                    />
                                                </section>
                                            ))}
                                        </div>
                                    )
                                }}
                            />
                        )}
                    </ChapterContext.Consumer>
                </div>
            </div>
        )
    }
}

export default Slides
