package de.kif.frontend.views.calendar

import de.kif.common.CALENDAR_GRID_WIDTH
import de.kif.common.ConstraintError
import de.kif.common.model.Schedule
import de.kif.common.model.WorkGroup
import de.westermann.kwebview.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.RepositoryDelegate
import de.kif.frontend.repository.ScheduleRepository
import de.kif.frontend.views.overview.getByClassOrCreate
import de.westermann.kwebview.*
import de.westermann.kwebview.components.Body
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLSpanElement
import org.w3c.dom.events.MouseEvent
import kotlin.browser.document
import kotlin.browser.window
import kotlin.dom.appendText
import kotlin.dom.isText
import kotlin.js.Date

class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : View(view) {

    private lateinit var mouseDelta: Point
    private var ignoreEditHover = false
    private var newCell: CalendarCell? = null

    private var language by dataset.property("language")

    var scheduleId = dataset["id"]?.toLongOrNull() ?: -1

    val schedule =
        RepositoryDelegate(ScheduleRepository, scheduleId)

    private lateinit var workGroup: WorkGroup

    var startTime = dataset["time"]?.toIntOrNull() ?: 0
    var length = dataset["length"]?.toIntOrNull() ?: 0

    var pending by classList.property("pending")
    private var error by classList.property("error")
    private var nextScroll = 0.0

    private val editable: Boolean
        get() = calendar.editable

    private var moveLockRoom: Long? = null
    private var moveLockTime: Int? = null

    private val nameView = view.getByClassOrCreate<HTMLSpanElement>("calendar-entry-name")

    private fun onMove(event: MouseEvent) {
        val position = event.toPoint() - mouseDelta

        if (ignoreEditHover) {
            for (element in document.elementsFromPoint(position.x, position.y)) {
                if (element.classList.contains("calendar-edit")) {
                    return
                }
            }
            ignoreEditHover = false
        }

        val cell = calendar.calendarCells.find {
            position in it.dimension
        }

        if (cell != null) {
            if (moveLockRoom != null && cell.roomId != moveLockRoom) {
                return
            }
            if (moveLockTime != null && cell.time != moveLockTime) {
                return
            }

            cell += this

            if (newCell == null) {
                style {
                    left = "0"
                    top = "0.1rem"
                }
            }

            async {
                val now = Date.now()
                if (now <= nextScroll) {
                    return@async
                }

                val width = calendar.calendar.calendarTable.clientWidth
                val height = window.innerHeight
                val rect = html.getBoundingClientRect()

                if (rect.left < 0.0) {
                    nextScroll = now + 500.0
                    calendar.calendar.scrollHorizontalBy(rect.left - 80.0)
                } else if (rect.right > width) {
                    nextScroll = now + 0.500
                    calendar.calendar.scrollHorizontalBy(rect.right - width + 50.0)
                }

                if (rect.top < 20.0) {
                    nextScroll = now + 500.0
                    calendar.calendar.scrollVerticalBy(rect.top - 50.0)
                } else if (rect.bottom > height - 20.0) {
                    nextScroll = now + 500.0
                    calendar.calendar.scrollVerticalBy(rect.bottom - height + 50.0)
                }
            }

            launch {
                calendarTools?.setName(cell.getRoom(), cell.time)
            }

            newCell = cell
        }

        event.preventDefault()
        event.stopPropagation()
    }

    private fun onFinishMove(event: MouseEvent) {
        classList -= "drag"

        newCell?.let { cell ->
            launch {
                val newTime = cell.time
                val newRoom = cell.getRoom()

                pending = true

                if (scheduleId < 0) {
                    ScheduleRepository.create(
                        Schedule(
                            null,
                            workGroup,
                            newRoom,
                            calendar.day,
                            newTime,
                            lockRoom = false,
                            lockTime = false
                        )
                    )
                    html.remove()
                } else {
                    ScheduleRepository.update(schedule.get().copy(room = newRoom, time = newTime))
                }
            }
        }
        newCell = null

        for (it in listeners) {
            it.detach()
        }
        listeners = emptyList()

        event.preventDefault()
        event.stopPropagation()
    }

    private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()

    fun startDrag() {
        classList += "drag"

        listeners = listOf(
            Body.onMouseMove.reference(this::onMove),
            Body.onMouseUp.reference(this::onFinishMove),
            Body.onMouseLeave.reference(this::onFinishMove)
        )
    }

    private val calendarTools = if (editable) CalendarTools(this) else null
    private val calendarErrors =  if (editable) CalendarErrors() else null

    init {
        onMouseDown { event ->
            if (!editable || event.target != html || "pending" in classList) {
                event.stopPropagation()
                return@onMouseDown
            }

            launch {
                val s = schedule.get()
                val time = s.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH

                val p = calendar.calendarCells.find {
                    it.day == s.day && it.time == time && it.roomId == s.room.id
                }?.dimension?.center ?: dimension.center

                mouseDelta = event.toPoint() - p

                moveLockRoom = if (s.lockRoom) s.room.id else null
                moveLockTime = if (s.lockTime) s.time else null

                startDrag()
            }
            event.preventDefault()
            event.stopPropagation()
        }

        if (calendarTools != null) {
            html.appendChild(calendarTools.html)

            launch {
                val s = schedule.get()
                calendarTools.update(s)
            }
        }
        if (calendarErrors != null) {
            html.appendChild(calendarErrors.html)
        }
    }

    fun load(schedule: Schedule) {
        pending = false

        if (schedule.id != null) scheduleId = schedule.id
        this.schedule.set(schedule)

        style {
            val pos = (schedule.time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()

            val ps = "${pos * 100}%"

            left = ps
            top = "calc($ps + 0.1rem)"
        }

        load(schedule.workGroup)
        calendarTools?.update(schedule)

        startTime = schedule.time
        length = schedule.workGroup.length

        val time = schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
        val cell = calendar.calendarCells.find {
            it.day == schedule.day && it.time == time && it.roomId == schedule.room.id
        }

        if (cell != null && cell.html != html.parentElement) {
            cell += this
        }
    }

    fun load(workGroup: WorkGroup) {
        pending = false

        language = workGroup.language.code
        this.workGroup = workGroup

        style {
            val size = workGroup.length / CALENDAR_GRID_WIDTH.toDouble()

            val sz = "${size * 100}%"

            width = sz
            height = "calc($sz - 0.2rem)"

            if (workGroup.track?.color != null) {
                backgroundColor = workGroup.track.color.toString()
                color = workGroup.track.color.calcTextColor().toString()
            }
        }

        nameView.textContent = workGroup.name
    }

    fun setError(errors: List<ConstraintError>) {
        error = errors.isNotEmpty()
        calendarErrors?.setErrors(errors)
    }

    companion object {
        fun create(calendar: CalendarBody, schedule: Schedule): CalendarEntry {
            val entry = CalendarEntry(calendar, createHtmlView())

            entry.load(schedule)

            return entry
        }

        fun create(calendar: CalendarBody, workGroup: WorkGroup): CalendarEntry {
            val entry = CalendarEntry(calendar, createHtmlView())

            entry.load(workGroup)
            entry.mouseDelta = Point.ZERO
            entry.ignoreEditHover = true
            entry.startDrag()

            return entry
        }
    }
}
