const fs = require("fs")
const { execSync } = require("child_process")
const postcssPlugin = require("esbuild-postcss")

function upsertEnvVar(filePath, key, value) {
    const nextLine = `${key}=${value}`
    const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : ""
    const lines = content ? content.split(/\r?\n/) : []
    const nextLines = []
    let replaced = false

    for (const line of lines) {
        if (!line || line.startsWith(`${key}=`)) {
            if (line.startsWith(`${key}=`)) {
                nextLines.push(nextLine)
                replaced = true
            } else {
                nextLines.push(line)
            }
            continue
        }
        nextLines.push(line)
    }

    if (!replaced) {
        if (nextLines.length && nextLines[nextLines.length - 1] !== "") {
            nextLines.push("")
        }
        nextLines.push(nextLine)
    }

    fs.writeFileSync(filePath, `${nextLines.join("\n").replace(/\n*$/, "")}\n`)
}

// Resolve version at build time via git describe (tag + hash), fallback to env var or "dev"
let gitHash = "dev"
try {
    gitHash = process.env.GIT_HASH || execSync("git describe --tags --always --dirty").toString().trim()
} catch (_) {
    // .git not available (e.g. Docker build without .git context)
}

// Generate buildInfo module that can be imported by frontend code
function writeBuildInfo() {
    const info = {
        gitHash,
        buildTime: new Date().toISOString(),
        assetVersion: `${gitHash}-${Date.now()}`,
    }
    fs.writeFileSync(
        __dirname + "/frontend/src/lib/buildInfo.ts",
        `// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nexport const gitHash = ${JSON.stringify(info.gitHash)}\nexport const buildTime = ${JSON.stringify(info.buildTime)}\nexport const assetVersion = ${JSON.stringify(info.assetVersion)}\n`
    )
    // Write same buildInfo for backend hooks (X-Build-Time / X-Release headers)
    fs.writeFileSync(
        __dirname + "/api/hooks/lib/buildInfo.js",
        `// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nmodule.exports = { gitHash: ${JSON.stringify(info.gitHash)}, buildTime: ${JSON.stringify(info.buildTime)}, assetVersion: ${JSON.stringify(info.assetVersion)} }\n`
    )
    upsertEnvVar(__dirname + "/api/config.yml.env", "ADMIN_ASSET_VERSION", info.assetVersion)
}
// NOTE: writeBuildInfo() is NOT called here at top-level.
// It is called by esbuild-wrapper.js before each build.
// This prevents the server build from overwriting the frontend's timestamp
// (which would cause a version mismatch and spurious auto-reload).

const resolvePlugin = {
    name: "resolvePlugin",
    setup(build) {
        let path = require("path")
        // url in css does not resolve via esbuild-svelte correctly
        build.onResolve({ filter: /.*/, namespace: "fakecss" }, (args) => {
            // console.log(args)
            if (args.path.match(/^\./)) return { path: path.dirname(args.importer) + "/" + args.path }
            // return { path: path.join(args.resolveDir, "public", args.path) }
        })
    },
}

// When MOCK is disabled, replace the mock module with a no-op stub so
// esbuild can tree-shake all mock data out of the production bundle.
const mockPlugin = {
    name: "mockPlugin",
    setup(build) {
        if (process.env.MOCK !== "1") {
            build.onResolve({ filter: /\/mock$/ }, (args) => {
                if (args.importer.includes("api.ts") || args.importer.includes("api.js")) {
                    return { path: "mock-noop", namespace: "mock-noop" }
                }
            })
            build.onLoad({ filter: /.*/, namespace: "mock-noop" }, () => ({
                contents: "export function mockApiRequest() { return null }",
                loader: "ts",
            }))
        }
    },
}

////////////////////////// esbuild-svelte

const sveltePlugin = require("esbuild-svelte")

const frontendDir = "./frontend"
const distDir = frontendDir + "/dist"

// console.log("copy public dir...")
// const copydir = require("copy-dir")
// copydir.sync(__dirname + "/public", __dirname + "/" + distDir)
/*copydir.sync(
    __dirname + "/public/index.html",
    __dirname + "/" + distDir + "/template.html"
)*/

const svelteConfig = require("./svelte.config")
const esbuildSvelte = sveltePlugin({
    compilerOptions: {
        css: "external",
        dev: (process.argv?.length > 2 ? process.argv[2] : "build") !== "build",
    },
    preprocess: svelteConfig.preprocess,
    cache: true,
    filterWarnings: (warning) => {
        // filter out a11y
        if (warning.code.match(/^a11y/)) return false
        return true
    },
})

const options = {
    logLevel: "info",
    color: true,
    entryPoints: ["./frontend/src/index.ts", "./frontend/src/admin.ts"],
    outdir: distDir,
    entryNames: "[name]",
    chunkNames: "chunks/[name]-[hash]",
    outExtension: { ".js": ".mjs" },
    metafile: true,
    format: "esm",
    minify: process.argv[2] == "build",
    bundle: true,
    splitting: true,
    define: {
        __MOCK__: process.env.MOCK === "1" ? "true" : "false",
    },
    plugins: [esbuildSvelte, postcssPlugin(), resolvePlugin, mockPlugin],
    loader: {
        ".woff2": "file",
        ".woff": "file",
        ".eot": "file",
        ".svg": "file",
        ".ttf": "file",
    },
    sourcemap: true,
    target: ["es2022", "chrome100", "firefox100", "safari15", "edge100"],
}

const bsMiddleware = []

if (process.argv[2] == "start") {
    const { createProxyMiddleware } = require("http-proxy-middleware")
    const dotEnv = fs.readFileSync(__dirname + "/.env", "utf8")
    const TIBI_NAMESPACE = dotEnv.match(/TIBI_NAMESPACE=(.*)/)[1]
    const apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + TIBI_NAMESPACE
    bsMiddleware.push(
        createProxyMiddleware({
            pathFilter: "/api",
            target: apiBase,
            pathRewrite: { "^/api": "" },
            changeOrigin: true,
            logLevel: "debug",
        })
    )

    // Sentry tunnel proxy: forwards /_s requests to Sentry ingest to avoid ad-blockers
    const https = require("https")
    const http = require("http")
    bsMiddleware.push(function (req, res, next) {
        if (req.url !== "/_s" || req.method !== "POST") return next()
        let body = []
        req.on("data", (chunk) => body.push(chunk))
        req.on("end", () => {
            const payload = Buffer.concat(body).toString()
            const firstLine = payload.split("\n")[0]
            let envelope
            try {
                envelope = JSON.parse(firstLine)
            } catch {
                res.writeHead(400)
                res.end()
                return
            }
            const dsn = envelope.dsn || ""
            let sentryUrl
            try {
                sentryUrl = new URL(dsn)
            } catch {
                res.writeHead(400)
                res.end()
                return
            }
            const projectId = sentryUrl.pathname.replace(/\//g, "")
            const ingestUrl = `${sentryUrl.protocol}//${sentryUrl.host}/api/${projectId}/envelope/`
            const transport = ingestUrl.startsWith("https") ? https : http
            const proxyReq = transport.request(
                ingestUrl,
                {
                    method: "POST",
                    headers: { "Content-Type": "application/x-sentry-envelope" },
                },
                (proxyRes) => {
                    res.writeHead(proxyRes.statusCode || 200)
                    proxyRes.pipe(res)
                }
            )
            proxyReq.on("error", () => {
                res.writeHead(502)
                res.end()
            })
            proxyReq.write(payload)
            proxyReq.end()
        })
    })

    if (process.env.SSR) {
        bsMiddleware.push(
            createProxyMiddleware({
                pathFilter: function (path, req) {
                    return !path.match(/\./)
                },
                target: apiBase,
                changeOrigin: true,
                logLevel: "debug",
                pathRewrite: function (path, req) {
                    return "/ssr?url=" + encodeURIComponent(path) + "&noCache=1"
                },
            })
        )
    }
}

// Pre-read config for tibi-server API reload (used by esbuild-wrapper.js)
let _tibiNamespace, _adminToken, _tibiReloadUrl
try {
    const dotEnv = fs.readFileSync(__dirname + "/.env", "utf8")
    _tibiNamespace = dotEnv.match(/TIBI_NAMESPACE=(.*)/)?.[1]
    const adminEnv = fs.readFileSync(__dirname + "/api/config.yml.env", "utf8")
    _adminToken = adminEnv.match(/ADMIN_TOKEN=(.*)/)?.[1]
    // Inside Docker compose, the tibiserver container is reachable by hostname
    _tibiReloadUrl = `http://tibiserver:8080/api/v1/_/${_tibiNamespace}/_/admin/reload`
} catch (_) {
    // Not critical — fallback: no reload capability
}

module.exports = {
    sveltePlugin: sveltePlugin,
    resolvePlugin: resolvePlugin,
    writeBuildInfo: writeBuildInfo,
    options: options,
    distDir,
    watch: {
        path: [
            __dirname + "/" + frontendDir + "/src",
            __dirname + "/api",
        ],
        // buildInfo.ts is written by writeBuildInfo() on every rebuild — ignoring
        // it prevents a rebuild loop. buildInfo.js and config.yml.env (ADMIN_ASSET_VERSION)
        // are also written, but those changes are debounced into a single reload.
        ignored: [
            /\/buildInfo\.ts$/,
        ],
    },
    tibiReloadUrl: _tibiReloadUrl,
    adminToken: _adminToken,
    serve: {
        onRequest(args) {
            console.log(args)
        },
    },
    browserSync: {
        server: {
            baseDir: frontendDir,
            middleware: [
                require("morgan")("dev"),
                ...bsMiddleware,
                require("connect-history-api-fallback")({
                    index: "/spa.html",
                    // Allow dots in URL paths (e.g. /version-3.7.1) to fall through to SPA,
                    // but only if they don't look like actual file requests.
                    rewrites: [
                        {
                            from: /\.(js|css|mjs|map|woff2?|ttf|eot|svg|png|jpe?g|gif|webp|ico|json|pdf)(\?.*)?$/i,
                            to: function (context) {
                                return context.parsedUrl.pathname
                            },
                        },
                        {
                            from: /./,
                            to: "/spa.html",
                        },
                    ],
                    // verbose: true,
                }),
            ],
        },
        open: false,
        // logLevel: "debug",
        ghostMode: false,
    },
}
