import { renderToString } from 'react-dom/server'
import { createElement } from 'react'

const cache = new Map<string, any>()

function respond(msg: Record<string, any>): void {
    Bun.write(Bun.stdout, JSON.stringify(msg) + '\n')
}

async function handleMessage(msg: any): Promise<void> {
    if (msg.method === 'ping') {
        respond({ id: msg.id, pong: true })
        return
    }

    if (msg.method === 'render') {
        const { file, props } = msg.params ?? {}

        if (!file) {
            respond({ id: msg.id, error: 'Missing file path' })
            return
        }

        try {
            let mod = cache.get(file)
            if (!mod) {
                mod = await import(file)
                cache.set(file, mod)
            }

            const Component = mod.default || Object.values(mod)[0]
            if (!Component) {
                respond({ id: msg.id, error: `No component exported from ${file}` })
                return
            }

            const html = renderToString(createElement(Component, props ?? {}))
            respond({ id: msg.id, html })
        } catch (e: any) {
            respond({ id: msg.id, error: e.message })
        }
        return
    }

    respond({ id: msg.id, error: `Unknown method: ${msg.method}` })
}

async function processInput(): Promise<void> {
    const reader = Bun.stdin.stream().getReader()
    const decoder = new TextDecoder()
    let buffer = ''

    while (true) {
        const { done, value } = await reader.read()
        if (done) break

        buffer += decoder.decode(value, { stream: true })

        let i: number
        while ((i = buffer.indexOf('\n')) !== -1) {
            const line = buffer.slice(0, i).trim()
            buffer = buffer.slice(i + 1)
            if (line) {
                try { await handleMessage(JSON.parse(line)) }
                catch (e: any) { respond({ id: -1, error: e.message }) }
            }
        }
    }
}

respond({ id: 0, ready: true })
processInput()
