import mime from "https://esm.sh/mime@4.0.3";
import { parseArgs } from "jsr:@std/cli/parse-args";
import bundler, {type ESBuild, type ImportMap} from "./mod.ts";
import Introspect from "./introspect.ts";

if(Deno.mainModule == import.meta.url)
{
    serve(parseArgs(Deno.args) as ServeArgs);
}

type ServeArgs = {path?:string, html?:string, port?:string|number};
async function serve(settings:ServeArgs):Promise<void>
{
    // etag hash
    const ETag = new Date().getTime().toString();

    // extra parsing
    const ExtensionsJS = ["tsx", "ts", "jsx", "js", "jsx", "mjs"];
    const PathExtensionType=(inPath:string)=>
    {
        const path = inPath.endsWith("/") ? inPath.substring(0, inPath.length-1) : inPath;
        const posDot = path.lastIndexOf(".");
        const extension = (posDot > path.lastIndexOf("/")) ? path.substring(posDot+1) : null;
        let type:string|null = null
        if(extension)
        {
            type = (ExtensionsJS.includes(extension)) ? "application/javascript" : mime.getType(extension);
        }

        return {path, extension, type};
    }

    // base path
    let basePath = "";
    let DenoConfig:ImportMap
    const SetDirectory =async(inPath:string)=>{
        if(inPath.startsWith("http://")||inPath.startsWith("https://")||inPath.startsWith("file://"))
        {
            basePath = inPath;
        }
        else
        {
            basePath = new URL(inPath, new URL(`file://${Deno.cwd().replaceAll("\\", "/")}/`).toString()).href;
        }
        if(!basePath.endsWith("/"))
        {
            basePath = basePath + "/";
        }
        console.log("Base Path:", basePath);

        DenoConfig = await Introspect(basePath) as ImportMap;
        console.log("found config", DenoConfig);
    }
    await SetDirectory(settings.path||"./");

    // bundler
    const transpiled:Map<string, string> = new Map();
    const defaultConfig:ESBuild.BuildOptions = { entryPoints:[] as string[], outdir:"/", entryNames: `[dir][name]`, splitting: true };
    const bundle =async(pathName:string, pathBase:string, buildConfig:ESBuild.BuildOptions = {}):Promise<string|void>=>
    {
        const lookupKey = pathName.substring(0, pathName.lastIndexOf("."))+".js";
        const lookup = transpiled.get(lookupKey);
        if(lookup)
        {
            return lookup;
        }
        buildConfig = {...defaultConfig, ...buildConfig}
        buildConfig.entryPoints = [pathBase+pathName];
        try
        {
            const results = await bundler(basePath, buildConfig, DenoConfig);
            if(results.outputFiles)
            {
                results.outputFiles.forEach(output=>{transpiled.set(output.path, output.text); console.log("building", output.path);})
                return results.outputFiles[0].text;
            }            
        }
        catch(_e){return;}
    };

    // index html serving
    const resp404 = new Response("404", {status:404});
    const resp304 = new Response(null, {status:304});
    let respIndexBody:false|string = false;
    if(settings.html)
    {
        if(settings.html.indexOf("<") != -1) // if html is actual markup instead of a url
        {
            respIndexBody = settings.html;
        }
        else
        {
            const load = await fetch(basePath+settings.html);
            respIndexBody = await load.text();
        }
    }

    ///// go
    Deno.serve({port:parseInt(settings.port as string)||8000}, async(req)=>
    {
        const checkHash = req.headers.get("if-none-match")
        if(checkHash === ETag){
            return resp304;
        }

        const url = new URL(req.url);
        const {path, type} = PathExtensionType(url.pathname);
        const headers = {"content-type":type||"", ETag, "Cache-Control":"max-age=3600"}

        // serve javascript
        if(type === "application/javascript")
        {
            const checkBuild = await bundle(path, basePath);
            if(checkBuild)
            {
                return new Response(checkBuild, {headers});
            }            
        }

        // serve static
        if(type)
        {
            try{
                const handle = await fetch(basePath+path.substring(1));
                return new Response(handle.body, {headers});
            }
            catch(_e){
                return resp404;
            }     
        }

        // serve routes/implied index html files
        if(respIndexBody)
        {
            return new Response(respIndexBody, {headers});
        }
        else
        {
            try{
                const handle = await fetch(basePath+path.substring(1));
                headers["content-type"] = "text/html"
                return new Response(handle.body, {headers});
            }
            catch(_e){
                return resp404;
            }
        }

    });
}

export default serve