/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/
/// <reference lib="dom" />
import { OVar, e } from "../jshelper/mod.ts";
import { Logger } from "../jshelper/src/log.ts";
import { Player } from "./player.ts"

export function playersync_controls(sync_state: OVar<undefined | Playersync>, player: Player) {
    let channel_name: HTMLInputElement;
    let channel_name_copy: HTMLInputElement;
    return e("div", { class: ["jsp-controlgroup", "jsp-playersync-controls"] },
        e("h3", "Playersync"),
        sync_state.map(sync => sync
            ? e("div",
                e("span", "Sync enabled."),
                e("button", "Disable", {
                    onclick: () => { sync_state.value?.destroy(); sync_state.value = undefined }
                }),
                e("p", "Session ID: ",
                    channel_name_copy = e("input", { type: "text", disabled: true, value: sync.name }),
                    e("button", "content_paste_go", {
                        class: "icon",
                        onclick: () => {
                            player.logger?.log("Session ID copied to clipboard.")
                            navigator.clipboard.writeText(channel_name_copy.value)
                        }
                    })
                ),
                e("h4", "Users"),
                sync.users.map(users =>
                    e("ul", ...[...users.keys()].map(u => e("li", u)))
                )
            )
            : e("div",
                channel_name = e("input", { type: "text", placeholder: "someroom:example.org" }),
                e("button", "Join", {
                    onclick: () => {
                        if (!channel_name.value.length) return
                        sync_state.value?.destroy()
                        sync_state.value = new Playersync(player, player.logger!, channel_name.value)
                    }
                }), e("br"),
                e("button", "Create new session", {
                    onclick: () => {
                        sync_state.value?.destroy()
                        sync_state.value = new Playersync(player, player.logger!)
                    }
                }))
        )
    )
}

function get_username() {
    return document.querySelector("nav .account .username")?.textContent ?? "Unknown User"
}

interface Packet {
    time?: number,
    playing?: boolean,
    join?: string,
    leave?: string,
}

export class Playersync {
    private ws: WebSocket
    private on_destroy: (() => void)[] = []

    public name: string
    public users = new OVar(new Map<string, null>())

    private cancel_pers: undefined | (() => void)
    set_pers(s?: string) {
        if (this.cancel_pers) this.cancel_pers(), this.cancel_pers = undefined
        if (s) this.cancel_pers = this.logger?.log_persistent(s)
    }

    constructor(private player: Player, private logger: Logger<string>, private channel_name?: string) {
        this.set_pers("Playersync enabling...")

        channel_name ??= Math.random().toString(16).padEnd(5, "0").substring(2).substring(0, 6)
        let [localpart, remotepart, port] = channel_name.split(":")
        if (!remotepart?.length) remotepart = globalThis.location.host
        if (port) remotepart += ":" + port
        this.name = localpart + ":" + remotepart

        this.ws = new WebSocket(`${globalThis.location.protocol.endsWith("s:") ? "wss" : "ws"}://${remotepart}/playersync/${encodeURIComponent(localpart)}`)
        this.on_destroy.push(() => this.ws.close())

        this.ws.onopen = () => {
            this.set_pers()
            this.logger.log(`Playersync connected.`)
            this.send({ join: get_username() })
        }
        this.ws.onerror = () => {
            this.set_pers(`Playersync websocket error.`)
        }
        this.ws.onclose = () => {
            this.set_pers(`Playersync websocket closed.`)
        }

        let last_time = 0;
        this.ws.onmessage = ev => {
            const packet: Packet = JSON.parse(ev.data)
            console.log("playersync recv", packet);
            if (packet.time !== undefined) {
                this.player.seek(packet.time)
                last_time = packet.time
            }
            if (packet.playing === true) this.player.play()
            if (packet.playing === false) this.player.pause()
            if (packet.join) {
                this.logger.log(`${packet.join} joined.`)
                this.users.value.set(packet.join, null)
                this.users.change()
            }
            if (packet.leave) {
                this.logger.log(`${packet.leave} left.`)
                this.users.value.delete(packet.leave)
                this.users.change()
            }
        }

        let cb: () => void

        const send_time = () => {
            const time = this.player.video.currentTime
            if (Math.abs(last_time - time) < 0.01) return
            this.send({ time: this.player.video.currentTime })
        }

        player.video.addEventListener("play", cb = () => {
            send_time()
            this.send({ playing: true })
        })
        this.on_destroy.push(() => player.video.removeEventListener("play", cb))

        player.video.addEventListener("pause", cb = () => {
            this.send({ playing: false })
            send_time()
        })
        this.on_destroy.push(() => player.video.removeEventListener("pause", cb))

        player.video.addEventListener("seeking", cb = () => {
            send_time()
        })
        this.on_destroy.push(() => player.video.removeEventListener("seeking", cb))
    }

    destroy() {
        this.set_pers()
        this.logger.log("Playersync disabled.")
        this.on_destroy.forEach(f => f())
    }

    send(p: Packet) {
        console.log("playersync send", p);
        this.ws.send(JSON.stringify(p))
    }
}

