import type { BunFile } from 'bun';
import path from 'path';
import { minify } from 'html-minifier';
import UglifyJS from 'uglify-js';

import wasmPlugin from './wasmPlugin';
import imagePlugin from './imagePlugin';
import fontPlugin from './fontPlugin';
import audioPlugin from './audioPlugin';
import filePlugin from './filePlugin';

import { getGames } from './isGame';

const b64 = async (file: string | BunFile) => (
    typeof file === 'string'
        ? Buffer.from(file)
        : Buffer.from(await file.arrayBuffer())
).toString('base64');

interface Args {
    production?: boolean;
    mobile?: boolean;
    local?: boolean;
}
export async function buildHTML(game: string, { production = false, mobile = false, local = false }: Args = {}) {
    const assetsDir = path.resolve(import.meta.dir, 'assets');
    const srcDir = path.resolve(import.meta.dir, '..', 'src');
    const gameDir = path.resolve(srcDir, 'games', game);
    const gameAssetsDir = path.resolve(gameDir, 'assets');

    const html = await Bun.file(path.resolve(assetsDir, local ? 'index-local.html' : 'index.html')).text();
    const bundle = await Bun.build({
        outdir: '/tmp',
        entrypoints: [path.resolve(srcDir, 'index.ts')],
        sourcemap: production ? 'none' : 'inline',
        define: {
            global: 'window',
            GAME: `"${game}"`,
            GAMES: JSON.stringify(await getGames()),
        },
        plugins: [
            imagePlugin,
            audioPlugin,
            fontPlugin,
            wasmPlugin({ production }),
            filePlugin,
        ]
    });

    if (bundle.success) {
        const scriptFile = bundle.outputs.find(a => a.kind === 'entry-point' && a.path.endsWith('.js'));
        if (!scriptFile) {
            console.error('No entry point found:', bundle.outputs);
            return;
        }
        const iconFile = Bun.file(path.resolve(gameAssetsDir, 'favicon.ico'));
        let icon = '';
        if (await iconFile.exists()) {
            icon = `<link rel="icon" href="data:;base64,${await b64(iconFile)}" />`;
        }
        let style = '';
        let styleFiles = bundle.outputs.filter(a => a.kind === 'asset' && a.path.endsWith('.css'));
        for (let styleFile of styleFiles) {
            style += await styleFile.text();
        }
        const title = game[0].toUpperCase() + game.slice(1).toLowerCase();
        let pwaIconFile = Bun.file(path.resolve(gameAssetsDir, 'pwa_icon.png'));
        if (!await pwaIconFile.exists()) {
            pwaIconFile = Bun.file(path.resolve(assetsDir, 'pwa_icon.png'));
        }
        let manifest = '';
        if (production && !local) {
            const pwaIcon = `data:image/png;base64,${await b64(pwaIconFile)}`;
            const publishURL = process.env.PUBLISH_URL ? `${process.env.PUBLISH_URL}${game}` : '.';
            const manifestJSON = JSON.stringify({
                name: title,
                short_name: title,
                start_url: publishURL,
                id: `/${game}`,
                display_override: ["window-controls-overlay"],
                display: "fullscreen",
                background_color: "#ffffff",
                theme_color: "#000000",
                icons: [{
                    src: pwaIcon,
                    sizes: '192x192',
                    type: 'image/png'
                }]
            });
            manifest = `<link rel="manifest" href="data:application/json;base64,${await b64(manifestJSON)}" />`;
        }
        let script = await scriptFile.text();
        const inits = new Set<string>();
        script = script.replace(/var (init_[^ ]+) = __esm\(\(\)/g, (_, $1) => {
            inits.add($1);
            return `var ${$1} = __esm(async ()`;
        });

        for (const init of inits) {
            script = script.replaceAll(`${init}()`, `await ${init}()`);
        }

        script = script.replaceAll('await Promise.resolve().then(() =>', '(');
        let scriptPrefix = '';
        if (production) {
            const minifyResult = UglifyJS.minify(script, {
                module: true,
                toplevel: true,
            });
            if (minifyResult.error) {
                console.warn(`Minify error: ${minifyResult.error}`);
            } else {
                script = minifyResult.code;
            }
        } else if (mobile) {
            const eruda = await Bun.file(path.resolve(import.meta.dir, '..', 'node_modules', 'eruda', 'eruda.js')).text();
            scriptPrefix = `<script>${eruda};\neruda.init();</script>`;
        }

        // function to avoid $& being replaced
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement
        const resultHTML = html
            .replace('<!--$SCRIPT$-->', () => `${scriptPrefix}<script type="module">${script}</script>`)
            .replace('<!--$TITLE$-->', () => title)
            .replace('<!--$ICON$-->', () => icon)
            .replace('<!--$MANIFEST$-->', () => manifest)
            .replace('/*$STYLE$*/', () => style);

        return minify(resultHTML, {
            collapseWhitespace: production,
            decodeEntities: true,
            minifyCSS: production,
            minifyJS: production,
        });
    } else {
        console.error('Failed: ', !bundle.success, bundle);
    }
}