import { get } from "svelte/store"
import type { Event } from "../../../types/Calendar"
import { STAGE } from "../../../types/Channels"
import { activeTimers, dictionary, events, nextActionEventPaused, nextActionEventStart, timers } from "../../stores"
import { isMainWindow, newToast } from "../../utils/common"
import { translateText } from "../../utils/language"
import { send } from "../../utils/request"
import { actionData } from "../actions/actionData"
import { customActionActivation, runAction } from "../actions/actions"
import { sortByClosestMatch } from "../actions/apiHelper"
import { getCurrentTimerValue, getTimerDynamicValue, playPauseGlobal } from "../drawer/timers/timers"
import { getDynamicValue } from "../edit/scripts/itemHelpers"
import { initPcoLiveSync } from "../../utils/pcoLiveSync"
import { clone, keysToID, sortByTime } from "./array"
import { loadShows } from "./setShow"
import { checkNextAfterMedia } from "./showActions"

const INTERVAL = 1000
const TEN_SECONDS = 1000 * 10
const ONE_MINUTE = 1000 * 60

let timeout: NodeJS.Timeout | null = null
let customInterval = INTERVAL
export function startTimer() {
    if (!isMainWindow()) return
    // if (!get(activeTimers).length || timeout) return
    if (!get(activeTimers).filter((a) => a.paused !== true).length || timeout) return

    if (timeout) clearTimeout(timeout)
    timeout = setTimeout(() => {
        activeTimers.update((timers) => clone(timers).map(increment))

        // send(OUTPUT, ["ACTIVE_TIMERS"], get(activeTimers))
        send(STAGE, ["ACTIVE_TIMERS"], get(activeTimers))

        timeout = null
        startTimer()
    }, customInterval)
}

export function startTimerByName(name: string) {
    if (name.includes("{")) name = getDynamicValue(name)
    const timersList = sortByClosestMatch(keysToID(get(timers)), name)
    const timerId = timersList[0]?.id
    if (!timerId) return

    startTimerById(timerId)
}

export function startTimerById(id: string) {
    const timer = get(timers)[id]
    if (!timer) return

    playPauseGlobal(id, timer, true)
}

export function stopTimers() {
    // timeout so timer_end action don't clear at the same time as next timer tick starts
    setTimeout(() => {
        // don't clear pcoLive entries
        activeTimers.update((a) => a.filter((t) => t.pcoLive))
        customInterval = INTERVAL
    }, 50)
}

function increment(timer: { id: string; start: number; end: number; [key: string]: any }, i: number) {
    // PCO Live timers update via pcoLiveSync
    if (timer.pcoLive) return timer

    if (timer.paused) {
        // has ended
        // if (timer.currentTime === timer.end && !timer.overflow) {
        //     // stop timers that have ended and is not outputted
        //     if (!isTimerOutputted(timer.id)) stopTimerById(timer.id)
        // }

        return timer
    }

    const startTime = timer.startDynamic !== undefined ? (getTimerDynamicValue(timer.startDynamic) ?? 0) : timer.start || 0
    const endTime = timer.endDynamic !== undefined ? (getTimerDynamicValue(timer.endDynamic) ?? 0) : timer.end || 0

    if (startTime < endTime ? timer.currentTime >= endTime && timer.currentTime < endTime + 1 : timer.currentTime <= endTime && timer.currentTime > endTime - 1) {
        if (!timer.overflow) timer.paused = true

        // ended
        checkNextAfterMedia(timer.id, "timer")
        customActionActivation("timer_end", timer.id)
    }

    if (timer.currentTime === endTime && !timer.overflow) return timer

    const currentTime = Date.now()
    // store timer start time (for accuracy)
    if (!timer.startTime) {
        const timerIs = timer.currentTime - startTime
        const timerStartShouldBe = timerIs * 1000 // - 1
        if (startTime < endTime) timer.startTime = currentTime - timerStartShouldBe
        else timer.startTime = currentTime + timerStartShouldBe
    }

    const difference = currentTime - timer.startTime
    const timerShouldBe = Math.floor(difference / 1000) + 1

    // prevent interval time increasing more and more
    if (i === 0) {
        const preciseTime = (timerShouldBe - 1) * 1000
        const differenceMs = difference - preciseTime
        customInterval = Math.max(500, INTERVAL - differenceMs)
    }

    if (startTime < endTime) timer.currentTime = startTime + timerShouldBe
    else timer.currentTime = startTime - timerShouldBe

    return timer
}

// auto stop any timer that is no longer in use
// function isTimerOutputted(timerId: string) {
//     const outputs = getAllActiveOutputs()
//     const outputSlideHasTimer =
//         outputs.some((output) => {
//             const outSlide = output.out?.slide
//             const outShow = get(showsCache)[outSlide?.id || ""]
//             if (!outShow) return false

//             const ref = getLayoutRef(outSlide?.id)
//             const slide = outShow.slides[ref[outSlide?.index ?? -1]?.id]
//             if (!slide) return false

//             const hasTimer = slide.items.some((item) => item.type === "timer" && item.timerId === timerId)
//             return hasTimer
//         })
//     if (outputSlideHasTimer) return true

//     // check stage

//     // might be in use by a dynamic value?

//     return false
// }

// convert "show" to "action" <= 1.1.7
let initialized = false
function convertShowToAction() {
    if (initialized) return
    initialized = true

    let updated = false
    const allEvents = get(events)
    Object.keys(allEvents).forEach((eventId) => {
        const newEvent = allEvents[eventId]
        if (newEvent.type !== "show") return

        updated = true
        newEvent.type = "action"
        newEvent.action = { id: "start_show", data: { id: newEvent.show } }

        allEvents[eventId] = newEvent
    })

    if (updated) events.set(allEvents)
}

let actionTimeout: NodeJS.Timeout | null = null
export function startEventTimer() {
    if (actionTimeout) return

    convertShowToAction()

    const currentTime: Date = new Date()
    let actionEvents: Event[] = Object.values(get(events)).filter((a) => {
        const eventTime: Date = new Date(a.from)
        return a.type === "action" && currentTime.getTime() - INTERVAL < eventTime.getTime()
    })

    if (!actionEvents.length) nextActionEventStart.set({})

    actionEvents = actionEvents.sort(sortByTime)

    actionTimeout = setTimeout(() => {
        actionEvents.forEach((event, i) => {
            if (!event.action) return

            const eventTime: Date = new Date(event.from)
            const toast = get(dictionary).toast || {}
            if (get(nextActionEventPaused)) return

            const actionId = event.action.id
            const actionName = translateText(actionData[actionId]?.name)

            const timeLeft: number = eventTime.getTime() - currentTime.getTime()
            if (i === 0) nextActionEventStart.set({ name: actionName, timeLeft })

            // less than 1 minute
            if (i < 4 && timeLeft <= ONE_MINUTE && timeLeft > ONE_MINUTE - INTERVAL) {
                newToast(`${toast.starting_action} "${actionName}" ${toast.less_than_minute}`)
                return
            }
            // less than 30 seconds
            if (i < 4 && timeLeft <= ONE_MINUTE / 2 && timeLeft > ONE_MINUTE / 2 - INTERVAL) {
                newToast(`${toast.starting_action} "${actionName}" ${toast.less_than_seconds.replace("{}", "30")}`)
                return
            }
            // less than 10 seconds
            if (i < 4 && timeLeft <= TEN_SECONDS && timeLeft > TEN_SECONDS - INTERVAL) {
                newToast(`${toast.starting_action} "${actionName}" ${toast.less_than_seconds.replace("{}", "10")}`)

                // preload data
                if (actionId === "start_show") loadShows([event.action.data?.id])
                return
            }

            // start action
            if (timeLeft <= 0 && timeLeft > 0 - INTERVAL) {
                newToast(`${toast.starting_action} "${actionName}" ${toast.now}`)

                runAction(convertEventAction(event.action), { source: "event" })
            }
        })

        actionTimeout = null
        startEventTimer()
    }, INTERVAL)
}

function convertEventAction(action) {
    return { triggers: [action.id], actionValues: { [action.id]: action.data || {} } }
}

// TOWARDS A TIME/EVENT

let timerCheckTimeout: NodeJS.Timeout | null = null
export function checkTimers() {
    if (timerCheckTimeout) clearTimeout(timerCheckTimeout)

    initPcoLiveSync(get(timers))

    Object.entries(get(timers)).forEach(([id, timer]) => {
        if (timer.type === "counter" || timer.type === "pco_live") return

        const time = getCurrentTimerValue({ ...timer, overflow: true }, {}, new Date())

        if (time < 0 && time >= -1) {
            checkNextAfterMedia(id, "timer")
            customActionActivation("timer_end", id)
        }
    })

    timerCheckTimeout = setTimeout(() => {
        timerCheckTimeout = null
        checkTimers()
    }, 1000)
}
