//go:build js && wasm

package wasmruntime

import (
	"syscall/js"

	"kjol/vdom"
)

var (
	rootRender      func()
	renderScheduled bool
	disposed        bool
	flushFunc       js.Func
	flushInited     bool
)

// HasServerContent reports whether #app already holds server-rendered markup
// (so the client should Hydrate rather than build from scratch).
func HasServerContent() bool {
	return document.Call("getElementById", "app").Get("firstChild").Truthy()
}

// Run mounts a component fresh (client-side rendering). The component returns a
// VNode tree; any signal write re-renders and reconciles into the DOM.
func Run(component func() *vdom.VNode) {
	vdom.Schedule = scheduleRender
	root := document.Call("getElementById", "app")
	var prev *vdom.VNode
	rootRender = func() {
		next := component()
		patchChildren(root, one(prev), one(next))
		prev = next
	}
	rootRender()
	finish(root)
}

// Hydrate attaches to server-rendered DOM: it renders the same initial tree the
// server did, adopts the existing nodes (wiring events, no re-creation), then
// re-renders reactively from there.
func Hydrate(component func() *vdom.VNode) {
	vdom.Schedule = scheduleRender
	root := document.Call("getElementById", "app")
	prev := component()
	hydrateNode(root.Get("firstChild"), prev)
	rootRender = func() {
		next := component()
		patchChildren(root, one(prev), one(next))
		prev = next
	}
	finish(root)
}

func finish(root js.Value) {
	// Allow a JS loader to tear this instance down before an in-place hot swap:
	// snapshot signal state (so the next instance restores it), then clear #app.
	js.Global().Set("__gowasmDispose", js.FuncOf(func(this js.Value, args []js.Value) any {
		disposed = true
		saveState()
		root.Set("innerHTML", "")
		return nil
	}))
	select {} // keep the runtime alive for event callbacks
}

func scheduleRender() {
	if disposed || rootRender == nil || renderScheduled {
		return
	}
	renderScheduled = true
	js.Global().Call("queueMicrotask", ensureFlush())
}

func ensureFlush() js.Func {
	if !flushInited {
		flushFunc = js.FuncOf(func(this js.Value, args []js.Value) any {
			renderScheduled = false
			rootRender()
			return nil
		})
		flushInited = true
	}
	return flushFunc
}
