//
// Modes
//

import { differenceInMinutes, format, startOfDay } from 'date-fns'

import _ from 'lodash'
import { useEffect, useRef, useState } from 'preact/hooks'
import { layoutIntervals } from '../interval-layout.js'

const WEEK_DAYS = ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato']

function hashString(str, seed = 0) {
    let h1 = 0xdeadbeef ^ seed,
        h2 = 0x41c6ce57 ^ seed
    for (let i = 0, ch; i < str.length; i++) {
        ch = str.charCodeAt(i)
        h1 = Math.imul(h1 ^ ch, 2654435761)
        h2 = Math.imul(h2 ^ ch, 1597334677)
    }
    h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
    h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
    return 4294967296 * (2097151 & h2) + (h1 >>> 0)
}

function normalizeCourseName(name) {
    return name
        .split(' ')
        .map(word => word.toLowerCase())
        .map(word => {
            if (word.trim().length === 0) return word

            return /(^del|^nel|^di$|^dei$|^con$|^alla$|^per$|^e$|^la$)/.test(word)
                ? word
                : word[0].toUpperCase() + word.slice(1)
        })
        .join(' ')
        .replaceAll('Ii', 'II')
        .replaceAll('Iii', 'III')
        .replaceAll(/'(.)/g, ({}, letter) => "'" + letter.toUpperCase())
}

const WorkWeekView = ({ events }) => {
    const eventsByWeekday = _.groupBy(events, event => event.start.getDay())

    // For each weekday compute the interval layout
    const rowLayouts = _.mapValues(eventsByWeekday, events =>
        layoutIntervals(
            events.map(e => ({
                start: differenceInMinutes(e.start, startOfDay(e.start)),
                end: differenceInMinutes(e.end, startOfDay(e.start)),
                data: e,
            }))
        )
    )

    return (
        <div class="work-week-h-view">
            <div class="week">
                {WEEK_DAYS.slice(1, 6).map((label, index) => (
                    <div class="day" style={{ '--size': rowLayouts[index + 1]?.length ?? 0 }}>
                        {label}
                    </div>
                ))}
            </div>
            <div class="events">
                <div class="header">
                    <div class="label" style={{ 'grid-column': '2 / span 2' }}>
                        9:00 &ndash; 11:00
                    </div>
                    <div class="label" style={{ 'grid-column': '4 / span 2' }}>
                        11:00 &ndash; 13:00
                    </div>
                    <div class="label" style={{ 'grid-column': '7 / span 2' }}>
                        14:00 &ndash; 16:00
                    </div>
                    <div class="label" style={{ 'grid-column': '9 / span 2' }}>
                        16:00 &ndash; 18:00
                    </div>
                </div>
                <div class="days">
                    {Object.values(rowLayouts).map(layout => (
                        <div class="day" style={{ '--size': layout.length }}>
                            {layout.map((events, rowIndex) =>
                                events.map(event => {
                                    const Local = () => {
                                        const eventRef = useRef()

                                        const updateMinWidth = () => {
                                            eventRef.current.style.minWidth =
                                                eventRef.current.offsetWidth + 'px'
                                        }

                                        useEffect(() => {
                                            if (eventRef.current) {
                                                setTimeout(updateMinWidth, 100)
                                                window.addEventListener('resize', updateMinWidth)

                                                return () => {
                                                    window.removeEventListener(
                                                        'resize',
                                                        updateMinWidth
                                                    )
                                                }
                                            }
                                        }, [eventRef.current])

                                        return (
                                            <div
                                                class="event"
                                                style={{
                                                    '--row': rowIndex + 1,
                                                    '--start': event.start / 60 - 7,
                                                    '--size': (event.end - event.start) / 60,
                                                    '--hue':
                                                        (Math.abs(
                                                            hashString('seed3' + event.data.name)
                                                        ) %
                                                            360) +
                                                        'deg',
                                                }}
                                                ref={eventRef}
                                            >
                                                {normalizeCourseName(event.data.name)}
                                            </div>
                                        )
                                    }

                                    return <Local />
                                })
                            )}
                        </div>
                    ))}
                </div>
            </div>
        </div>
    )
}

const WorkWeekVerticalView = ({ events, selection, setSelection, hideOtherCourses }) => {
    const selectionSet = new Set(selection)

    // const base = {
    //     1: [],
    //     2: [],
    //     3: [],
    //     4: [],
    //     5: [],
    // }

    const eventsByWeekday = _.groupBy(
        !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.name)),
        event => event.start.getDay()
    )

    // const dayIntervalLayout = _.mapValues(Object.assign(base, eventsByWeekday), events =>
    const dayIntervalLayout = _.mapValues(eventsByWeekday, events =>
        layoutIntervals(
            events.map(e => ({
                start: differenceInMinutes(e.start, startOfDay(e.start)),
                end: differenceInMinutes(e.end, startOfDay(e.start)),
                data: e,
            }))
        )
    )

    const [currentlyHovered, setCurrentlyHovered] = useState(null)
    const element = useRef()

    useEffect(() => {
        if (element.current) {
            const l = e => {
                const $event = e.target.closest('.event')
                if ($event) {
                    setCurrentlyHovered($event.dataset.eventId)
                } else {
                    setCurrentlyHovered(null)
                }
            }

            element.current.addEventListener('mousemove', l)
            element.current.addEventListener('mouseleave', l)

            return () => {
                element.current.removeEventListener('mousemove', l)
                element.current.removeEventListener('mouseleave', l)
            }
        }
    }, [element.current])

    return (
        <div class="work-week-v-view" ref={element}>
            <div class="pivot"></div>
            <div class="left-header">
                <div class="blocks">
                    <div
                        class="block skip-border"
                        style={{
                            '--start': 9 - 7,
                            '--size': 2,
                        }}
                    >
                        9:00 &ndash; 11:00
                    </div>
                    <div
                        class="block"
                        style={{
                            '--start': 11 - 7,
                            '--size': 2,
                        }}
                    >
                        11:00 &ndash; 13:00
                    </div>
                    <div
                        class="block skip-border"
                        style={{
                            '--start': 14 - 7,
                            '--size': 2,
                        }}
                    >
                        14:00 &ndash; 16:00
                    </div>
                    <div
                        class="block"
                        style={{
                            '--start': 16 - 7,
                            '--size': 2,
                        }}
                    >
                        16:00 &ndash; 18:00
                    </div>
                </div>
            </div>
            {Object.entries(dayIntervalLayout).map(([index, layout]) => (
                <div class="day" style={{ '--size': Math.max(1, layout.length) }}>
                    <div class="top-header">{WEEK_DAYS[parseInt(index)]}</div>
                    <div class="events">
                        {layout.map((events, stackIndex) => (
                            <>
                                {events.map(event => (
                                    <div
                                        class={
                                            'event' +
                                            (currentlyHovered === event.data.name
                                                ? ' highlight'
                                                : '') +
                                            (selectionSet.has(event.data.name) ? ' selected' : '')
                                        }
                                        data-event-id={event.data.name}
                                        style={{
                                            '--start': event.start / 60 - 7,
                                            '--stack': stackIndex + 1,
                                            '--size': (event.end - event.start) / 60,
                                            '--hue':
                                                (Math.abs(hashString('seed3' + event.data.name)) %
                                                    360) +
                                                'deg',
                                        }}
                                        onClick={() => {
                                            if (!selectionSet.has(event.data.name))
                                                setSelection([...selection, event.data.name])
                                            else
                                                setSelection(
                                                    selection.filter(
                                                        name => event.data.name !== name
                                                    )
                                                )
                                        }}
                                    >
                                        <div class="title">
                                            {normalizeCourseName(event.data.name)}
                                        </div>
                                        <div class="aula">{event.data.aula}</div>
                                    </div>
                                ))}
                            </>
                        ))}

                        {/* Grid Tracks */}
                        {[1, 3, 5].map(i => (
                            <div class="grid-line-h" style={{ '--track': i }}></div>
                        ))}
                        {[6, 8, 10].map(i => (
                            <div class="grid-line-h" style={{ '--track': i }}></div>
                        ))}
                    </div>
                </div>
            ))}
        </div>
    )
}

const CourseView = ({ events, selection, setSelection, hideOtherCourses }) => {
    const selectionSet = new Set(selection)

    const visibleEvents = !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.name))

    const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'name'), 'name')

    const [currentlyHovered, setCurrentlyHovered] = useState(null)
    const element = useRef()

    useEffect(() => {
        if (element.current) {
            const l = e => {
                const $course = e.target.closest('.course')
                if ($course) {
                    setCurrentlyHovered($course.dataset.courseId)
                } else {
                    setCurrentlyHovered(null)
                }
            }

            element.current.addEventListener('mousemove', l)
            element.current.addEventListener('mouseleave', l)

            return () => {
                element.current.removeEventListener('mousemove', l)
                element.current.removeEventListener('mouseleave', l)
            }
        }
    }, [element.current])

    return (
        <div class="course-view" ref={element}>
            <div class="wrap-container">
                {Object.entries(eventsByCourse).map(([name, courseEvents]) => (
                    <div
                        class={
                            'course' +
                            (currentlyHovered === name ? ' highlight' : '') +
                            (selectionSet.has(name) ? ' selected' : '')
                        }
                        data-course-id={name}
                        onClick={() => {
                            if (!selectionSet.has(name)) setSelection([...selection, name])
                            else setSelection(selection.filter(n => n !== name))
                        }}
                    >
                        <div class="title">{normalizeCourseName(name)}</div>
                        <div class="docenti">{courseEvents[0].docenti.join(', ')}</div>
                        <div class="events">
                            {courseEvents.map(course => (
                                <div>
                                    {WEEK_DAYS[course.start.getDay()]}{' '}
                                    {format(course.start, 'H:mm')} &ndash;
                                    {format(course.end, 'H:mm')} {course.aula}
                                </div>
                            ))}
                        </div>
                    </div>
                ))}
            </div>
        </div>
    )
}

//
// EventsView
//

const viewModeMap = {
    'work-week-h': WorkWeekView,
    'work-week-v': WorkWeekVerticalView,
    'course': CourseView,
}

export const EventsView = ({ mode, ...viewProps }) => {
    const Mode = viewModeMap[mode]

    return (
        <div class="events-view">
            <Mode {...viewProps} />
        </div>
    )
}
