import path from "path";
import { WORKING_DIR } from "../constants";
import fs from 'fs/promises'
import JSZip from "jszip";
import crypto from "crypto";
import { Logger } from "../logger";

const logger = Logger('compiler');

export interface BundleFile {
    file: string;
    type: string;
    main: boolean;
}

const binaryFileTypes = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'mp4', 'mp3', 'wav', 'ogg', 'pdf', 'zip', 'rar', '7z'];


export async function compileBundleFile(zip : JSZip) {
    let timestart = Date.now();
    logger.log("Starting compilation of bundle file");
    let files : BundleFile[] = await getAllFilesInBundleForCompile(zip);
    if(files.length == 0){
        logger.log("No files to compile in bundle");
        throw new Error("No files to compile in bundle");
    }
    let compileOutputDir = path.join(WORKING_DIR, 'b');
    await fs.mkdir(compileOutputDir, { recursive: true });

    let mainFiles = files.filter(f => f.main);
    let otherFiles = files.filter(f => !f.main);

    logger.log(`Compiling ${files.length} files, ${mainFiles.length} main files and ${otherFiles.length} other files`);

    for (let file of files) {
        let zipFile = zip.file(file.file);
        if (zipFile) {
            let content = await zipFile.async('nodebuffer');
            let outputPath = path.join(compileOutputDir, await hashFileName(path.basename(file.file)));
            await fs.writeFile(outputPath, content);
        }
    }
    
    await compileHtmlFile(mainFiles);
    await transitionFilenamesInFiles([...mainFiles, ...otherFiles]);
    logger.log("Compilation done successfully in " + (Date.now() - timestart) + "ms");
}

async function transitionFilenamesInFiles(otherFiles: BundleFile[]) {
    logger.log(`Transitioning filenames in ${otherFiles.length} files`);
    for(let i = 0; i < otherFiles.length; i++){
        let file = otherFiles[i];
        let filePath = path.join(WORKING_DIR, 'b', await hashFileName(path.basename(file.file)));
        let buffer = await fs.readFile(filePath, {
            encoding: file.type && binaryFileTypes.includes(file.type) ? 'binary' : 'utf-8'
        });
        let content = buffer.toString();
        logger.log(`Processing file ${path.basename(file.file)}`);

        for(let j = 0; j < otherFiles.length; j++){
            let targetFile = otherFiles[j];
            let originalName = path.basename(targetFile.file);
            let hashedName = await hashFileName(originalName);
            let entries = content.split(originalName);
            if(entries.length <= 0){
                continue;
            }
            logger.log(`In file ${path.basename(file.file)} replacing ${originalName} with ${hashedName} (${entries.length - 1} occurrences)`);
            content = entries.join(hashedName);
        }
        logger.log(`Flush transitioned file ${path.basename(file.file)}`);
        await fs.writeFile(filePath, content, {
            encoding: file.type && binaryFileTypes.includes(file.type) ? 'binary' : 'utf-8'
        });
    }
}

async function compileHtmlFile(mainFiles: BundleFile[]) {
    let html = await constructHtmlTemplateToCompile();
    html = html.replace(`<script type="module" src="/renderer.tsx"></script>`, '');
    let scripts = mainFiles.filter(f => f.type === 'js').map(f => f.file);
    let styles = mainFiles.filter(f => f.type === 'css').map(f => f.file);

    let scriptTags : string = "";
    for (let script of scripts) {
        let hashedName = await hashFileName(path.basename(script));
        scriptTags += `<script type="module" src="${hashedName}"></script>\n`;
    }

    let styleTags : string = "";
    for (let style of styles) {
        let hashedName = await hashFileName(path.basename(style));
        styleTags += `<link rel="stylesheet" href="${hashedName}">\n`;
    }


    html = html.replace('<!--RR-->', `${scriptTags}\n${styleTags}`);
    html = html.replace('\n', '');


    let outputHtmlPath = path.join(WORKING_DIR, 'b', 'j.html');
    await fs.writeFile(outputHtmlPath, html);
}

export async function getAllFilesInBundleForCompile(zip : JSZip) : Promise<BundleFile[]> {
    const mainFilePrefixes = ['index-', 'main-'];
    return Object.keys(zip.files)
        .filter(filePath => filePath.indexOf('__MACOSX') == -1)
        .map(filePath => {
            const ext = path.extname(filePath).slice(1);
            const isMain = mainFilePrefixes.some(prefix => path.basename(filePath).startsWith(prefix));
            return {
                file: filePath,
                type: ext,
                main: isMain
            } as BundleFile;
    });
}   

async function constructHtmlTemplateToCompile() {
    let buffer = await fs.readFile(
        path.join(__dirname, '../../resources/prod.html')
    );
    let html = buffer.toString();
    return html;
}

async function hashFileName(name : string) : Promise<string> {
    return crypto.createHash('md5').update(name).digest('hex') + path.extname(name);
}