const esbuild = require("esbuild")
const fs = require("fs")
const http = require("http")
const path = require("path")

const config = require(process.cwd() + (process.argv?.length > 3 ? "/" + process.argv[3] : "/esbuild.config.js"))
const { watch } = require("chokidar")

function log(str, clear) {
    if (clear && process.stdout.cursorTo && process.stdout.clearScreenDown) {
        process.stdout.cursorTo(0, 0)
        process.stdout.clearScreenDown()
    }
    console.log("\x1b[36m%s\x1b[0m", str)
}

let buildResults
let ctx

function cleanChunks() {
    const outdir = config.options.outdir
    if (!outdir) return
    const chunksDir = path.join(outdir, "chunks")
    if (fs.existsSync(chunksDir)) {
        fs.rmSync(chunksDir, { recursive: true })
    }
}

async function build(catchError) {
    if (config.writeBuildInfo) {
        config.writeBuildInfo()
    }
    cleanChunks()
    if (!ctx) ctx = await esbuild.context(config.options)
    log((buildResults ? "re" : "") + "building...")
    const timerStart = Date.now()
    try {
        buildResults = await ctx.rebuild()
        if (config.options.metafile) {
            fs.writeFileSync(
                (config.options.outfile ? path.dirname(config.options.outfile) : config.options.outdir) + "/meta.json",
                JSON.stringify(buildResults.metafile, null, 4)
            )
        }
    } catch (e) {
        console.log(e)
        if (!catchError) throw e
    }

    const timerEnd = Date.now()
    log(`built in ${timerEnd - timerStart}ms.`)
}

let bs
switch (process.argv?.length > 2 ? process.argv[2] : "build") {
    case "serve":
        console.log("\x1b[36m%s\x1b[0mserving...")
        esbuild.context(config.options).then(function (_ctx) {
            _ctx.serve(config.serve).catch((err) => {
                console.error(err)
                process.exit(1)
            })
        })
        break
    case "start":
        bs = require("browser-sync")
        bs.init(config.browserSync)
    case "watch":
        // config.options.incremental = true
        build(true).then(() => {
            if (bs) {
                bs.reload()
            }
        })
        // Trigger tibi-server project reload for api/ changes
        const apiDir = path.resolve("api") + "/"
        let reloadInFlight = false
        let reloadQueued = false
        function reloadTibi() {
            if (!config.tibiReloadUrl || !config.adminToken) {
                log("tibi-server reload: missing URL or token (running in mock mode?)")
                return
            }
            if (reloadInFlight) {
                reloadQueued = true
                log("tibi-server reload: already in flight, queuing another...")
                return
            }
            reloadInFlight = true
            const parts = new URL(config.tibiReloadUrl)
            const opts = {
                hostname: parts.hostname,
                port: parts.port,
                path: parts.pathname,
                method: "POST",
                headers: { Authorization: "Bearer " + config.adminToken },
                timeout: 5000,
            }
            let body = ""
            const finishReload = () => {
                reloadInFlight = false
                if (reloadQueued) {
                    reloadQueued = false
                    log("tibi-server reload: executing queued reload...")
                    reloadTibi()
                }
            }
            const req = http.request(opts, (res) => {
                res.on("data", (chunk) => (body += chunk))
                res.on("end", () => {
                    if (res.statusCode === 200) {
                        log("tibi-server reloaded ✓")
                    } else {
                        console.error(
                            "\x1b[33mtibi-server reload: HTTP " + res.statusCode + " — " + body.trim() + "\x1b[0m"
                        )
                    }
                    finishReload()
                })
            })
            req.on("error", (e) => {
                console.error("\x1b[33mtibi-server reload failed: " + e.message + "\x1b[0m")
                finishReload()
            })
            req.end()
        }

        const { path: watchPaths, ignored: watchIgnored, ...watchRest } = config.watch
        const watcher = watch(watchPaths, { ignored: watchIgnored, ...watchRest })
        log("watching files...")
        const DEBOUNCE_MS = 200
        let frontendTimer, apiTimer
        watcher.on("change", function (changedPath) {
            log(`${changedPath} changed`, true)
            if (changedPath.startsWith(apiDir)) {
                clearTimeout(apiTimer)
                apiTimer = setTimeout(() => {
                    log("api/ change detected → reloading tibi-server...")
                    reloadTibi()
                }, DEBOUNCE_MS)
            } else {
                clearTimeout(frontendTimer)
                frontendTimer = setTimeout(() => {
                    build(true).then(() => {
                        if (bs) {
                            bs.reload()
                        }
                    })
                }, DEBOUNCE_MS)
            }
        })
        break
    default:
        if (config.writeBuildInfo) {
            config.writeBuildInfo()
        }
        cleanChunks()
        esbuild.build(config.options).then(function (buildResults) {
            if (config.options.metafile) {
                fs.writeFileSync(
                    (config.options.outfile ? path.dirname(config.options.outfile) : config.options.outdir) +
                        "/meta.json",
                    JSON.stringify(buildResults.metafile, null, 4)
                )
            }
        })
}
