////
// Hosting for your NOSE webapps!

import type { Child } from "hono/jsx"
import { type Context, Hono } from "hono"
import { renderToString } from "hono/jsx/dom/server"
import { join, dirname } from "path"
import { readdirSync } from "fs"

import { NOSE_WWW } from "./config"
import { isFile, isDir } from "./utils"

export type Handler = (r: Context) => string | Child | Response | Promise<Response>
export type App = Hono | Handler

export async function serveApp(c: Context, subdomain: string): Promise<Response> {
    const app = await findApp(subdomain)
    const path = appDir(subdomain)

    if (!path) return c.text(`App not found: ${subdomain}`, 404)

    const staticPath = join(path, "pub", c.req.path === "/" ? "/index.html" : c.req.path)

    if (isFile(staticPath))
        return serveStatic(staticPath)

    if (!app) return c.text(`App not found: ${subdomain}`, 404)

    if (app instanceof Hono)
        return app.fetch(c.req.raw)
    else
        return toResponse(await app(c))
}

export function apps(): string[] {
    const apps: string[] = []

    for (const entry of readdirSync(NOSE_WWW))
        apps.push(entry.replace(/\.tsx?/, ""))

    return apps.sort()
}

export function appDir(name: string): string | undefined {
    const path = [
        `${name}.ts`,
        `${name}.tsx`,
        name
    ]
        .map(path => join(NOSE_WWW, path))
        .flat()
        .filter(path => /\.tsx?$/.test(path) ? isFile(path) : isDir(path))[0]

    if (!path) return

    return /\.tsx?$/.test(path) ? dirname(path) : path
}

async function findApp(name: string): Promise<App | undefined> {
    const paths = [
        `${name}.ts`,
        `${name}.tsx`,
        join(name, "index.ts"),
        join(name, "index.tsx")
    ]

    let app

    for (const path of paths) {
        app = await loadApp(join(NOSE_WWW, path))
        if (app) return app
    }
}

async function loadApp(path: string): Promise<App | undefined> {
    if (!await Bun.file(path).exists()) return

    const mod = await import(path + `?t=${Date.now()}`)
    if (mod?.default)
        return mod.default as App
}

export function toResponse(source: string | Child | Response): Response {
    if (source instanceof Response)
        return source
    else if (typeof source === "string")
        return new Response(source)
    else
        return new Response(renderToString(source), {
            headers: {
                "Content-Type": "text/html; charset=utf-8"
            }
        })
}

function serveStatic(path: string): Response {
    const file = Bun.file(path)
    return new Response(file, {
        headers: {
            "Content-Type": file.type
        }
    })
}