#!/usr/bin/env -S bun run
import { $ } from "bun";
import { readdir } from "node:fs/promises" 
import { parseArgs } from "util";
import { JSDOM } from "jsdom";
import { Dirent } from "node:fs";
import { mkdir, stat } from "node:fs/promises";


enum LogLevel {
    DEBUG = 0,
    INFO = 1,
    WARN = 2,
    ERROR = 3,
    FATAL = 4
}
let {DEBUG, INFO, WARN, ERROR, FATAL} = LogLevel
let minLogLevel : LogLevel = INFO
function log(level: LogLevel, message: any) {
    const p = Bun.argv[1]?.split("/").slice(-1)[0] ?? "build script";
    switch (level) {
        case DEBUG: if (minLogLevel <= DEBUG) console.log(`\x1b[90m${p}: \x1b[1;45;97m [DEBUG] \x1b[0m ${message}`); break;
        case INFO: if (minLogLevel <= INFO) console.log(`\x1b[90m${p}: \x1b[1;44;97m [INFO] \x1b[0m ${message}`); break;
        case WARN: if (minLogLevel <= WARN) console.log(`\x1b[90m${p}: \x1b[1;48;5;208;97m [WARN] \x1b[0m ${message}`); break;
        case ERROR: if (minLogLevel <= ERROR) console.log(`\x1b[90m${p}: \x1b[1;41;97m [ERROR] \x1b[0m ${message}`); break;
        case FATAL: /*Cannot suppress fatal*/console.log(`\x1b[90m${p}: \x1b[1;41;97m [FATAL]  ${message} \x1b[0m`); process.exit(); break;
        default: log(FATAL, `${level} isn't a valid log level`);
    }
}
log(INFO, "Annwan’s wiki’s custom build script v2")

async function build_typst_file(file: Dirent) {
    let path = file.parentPath + "/" + file.name
    let dir = "public_html" + file.parentPath.slice(3)
    let slug = path.slice(4, -4)
    let outfile = `public_html/${slug}.html`
    log(INFO, `Compiling ${slug}`)
    await mkdir(dir, {recursive: true})
    await $`typst c --features html --format html --root . ${path} ${outfile}`
    log(DEBUG, `Checking if ${slug} needs post processing`)
    let html = await Bun.file(outfile).text();
    let heads = html.match(new RegExp("<head>.*?</head>", "sg"));
    if (heads != null && heads.length >= 2) {
        log(DEBUG, `Postprocessing ${slug}`)
        html = html.replace(heads[1], "");
        html = html.replace(heads[0], heads[1]);
        await Bun.write(outfile, html);
    }
    log(INFO, `${slug} done`)
}


async function build_all_typst() {
    let files = await readdir("src", {withFileTypes: true, recursive: true});    
    let promises: Promise<void>[] = [];
    for (let f of files) {
        if (f.isFile() && f.name.endsWith(".typ")) 
        promises.push(build_typst_file(f))
    }
    await Promise.all(promises)
}

async function copy_assets() {
    log(INFO, "Updating assets")
    await $`rm -rf ${minLogLevel == DEBUG ? "-v" : ""} public_html/assets/`
    await $`cp -r ${minLogLevel == DEBUG ? "-v" : ""} assets/ public_html/`
    await $`cp ${minLogLevel == DEBUG ? "-v" : ""} public_html/assets/favicon.ico public_html/favicon.ico`
}

type IndexEntry = { id: string; title: string; body: string; tags: string;}
async function collect_data(f:string) : Promise<IndexEntry> {
    let id = f.slice(12, -5)
    log(INFO, `collecting metadata for ${id}`)
    let dom = await JSDOM.fromFile(f)
    log(DEBUG, `${id}: read`)
    let things = dom.window.document.getElementsByTagName("main")
    let body = ""
    for (let thing of things) body += thing.textContent;
    log(DEBUG, `${id}: body extracted`)
    let title = dom.window.document.getElementById("search-title")?.getAttribute("content") ?? ""
    log(DEBUG, `${id} title extracted: ${title}`)
    let tags = dom.window.document.getElementById("search-tags")?.getAttribute("content") ?? ""
    log(DEBUG, `${id}: tags extracted: ${tags}`)
    return {id, title, body, tags}
}
async function gen_index() {
    let files = await readdir("public_html", {withFileTypes: true, recursive: true})
    log(INFO, "Generating the search index");
    let promises : Promise<IndexEntry>[] = [];
    for (let f of files) {
        if (f.isFile() && f.name.endsWith(".html")) {
            promises.push(collect_data(f.parentPath + "/" + f.name));
        }
    }
    let data : IndexEntry[] = await Promise.all(promises)
    log(INFO, `Writing index`)
    await Bun.write("public_html/index.json", JSON.stringify(data), {mode: 0o644, createPath: true})
}


const args = parseArgs({
    args: Bun.argv,
    options: {
        "build": {type: "boolean", short: "b"},
        "assets": {type: "boolean", short: "a"},
        "index": {short: "i", type: "boolean" },
        "verbose": {short: "v", type: "string"},
        "help": {type: "boolean", short: "h"}
    },
    allowPositionals: true
}).values

if (args.verbose)  {
    try {
        minLogLevel = parseInt(args.verbose);
    } catch {
        log(FATAL, `'${args.verbose}' isn't a number`);
    }
}

if (args.help) {
    log(INFO, `USAGE:`)
    log(INFO, `\t${Bun.argv[1]} [-b|--build] [-a|--assets] [-i|--index] [-v ⟨n⟩|--verbose ⟨n⟩]`)
    log(INFO, `\t${Bun.argv[1]} -h|--help`)
} else {

if (!args.build && !args.assets && !args.index) {
    await build_all_typst()
    await copy_assets()
    await gen_index()
}
if (args.build) await build_all_typst();
if (args.assets) await copy_assets();
if (args.index) await gen_index();
log(INFO, "DONE");
}
