/*
 * Copyright (C) 2024-present Puter Technologies Inc.
 *
 * This file is part of Puter.
 *
 * Puter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
import { encode } from 'html-entities';
import fs from 'fs';
import path from 'path';
import webpack from 'webpack';
import CleanCSS from 'clean-css';
import uglifyjs from 'uglify-js';
import { lib_paths, css_paths, js_paths } from './src/static-assets.js';
import { fileURLToPath } from 'url';
import BaseConfig from './webpack/BaseConfig.cjs';

// Polyfill __dirname, which doesn't exist in modules mode
const __dirname = path.dirname(fileURLToPath(import.meta.url));

/**
 * Builds the application by performing various tasks such as cleaning the distribution directory,
 * merging and minifying JavaScript and CSS files, bundling GUI core files, handling images,
 * and preparing the final distribution package. The process involves concatenating library
 * scripts, optimizing them for production, and copying essential assets to the distribution
 * directory. The function also supports a verbose mode for logging detailed information during
 * the build process.
 *
 * @param {Object} [options] - Optional parameters to customize the build process.
 * @param {boolean} [options.verbose=false] - Specifies whether to log detailed information during the build.
 *
 * @async
 * @returns {Promise<void>} A promise that resolves when the build process is complete.
 *
 * @example
 * build({ verbose: true }).then(() => {
 *   console.log('Build process completed successfully.');
 * }).catch(error => {
 *   console.error('Build process failed:', error);
 * });
 */
async function build(options){
    const PUTER_ENV = process.env.PUTER_ENV || 'prod';
    // -----------------------------------------------
    // Delete ./dist/ directory if it exists and create a new one
    // -----------------------------------------------
    if(fs.existsSync(path.join(__dirname, 'dist'))){
        fs.rmSync(path.join(__dirname, 'dist'), {recursive: true});
    }
    fs.mkdirSync(path.join(__dirname, 'dist'));

    // -----------------------------------------------
    // Concat/merge the JS libraries and save them to ./dist/libs.js
    // -----------------------------------------------
    let js = '';
    for(let i = 0; i < lib_paths.length; i++){
        const file = path.join(__dirname, 'src', lib_paths[i]);
        // js
        if(file.endsWith('.js') && !file.endsWith('.min.js')){
            let minified_code = await uglifyjs.minify(fs.readFileSync(file).toString(), {mangle: false});
            if(minified_code && minified_code.code){
                js += minified_code.code;
                if(options?.verbose)
                    console.log('minified: ', file);
            }
        }else{
            js += fs.readFileSync(file);
            if(options?.verbose)
                console.log('skipped minification: ', file);
        }

        js += '\n\n\n';
    }

    // -----------------------------------------------
    // Combine all images into a single js file
    // -----------------------------------------------
    let icons = 'window.icons = [];\n\n\n';
    fs.readdirSync(path.join(__dirname, 'src/icons')).forEach(file => {
        // skip dotfiles
        if(file.startsWith('.'))
            return;
        // load image
        let buff = new Buffer.from(fs.readFileSync(path.join(__dirname, 'src/icons') + '/' + file));
        // convert to base64
        let base64data = buff.toString('base64');
        // add to `window.icons`
        if(file.endsWith('.png'))
            icons += `window.icons['${file}'] = "data:image/png;base64,${base64data}";\n`;
        else if(file.endsWith('.svg'))
            icons += `window.icons['${file}'] = "data:image/svg+xml;base64,${base64data}";\n`;
    });

    // -----------------------------------------------
    // concat/merge the CSS files and save them to ./dist/bundle.min.css
    // -----------------------------------------------
    let css = '';
    for(let i = 0; i < css_paths.length; i++){
        let fullpath = path.join(__dirname, 'src', css_paths[i]);
        // minify CSS files if not already minified, then concatenate
        if(css_paths[i].endsWith('.css') && !css_paths[i].endsWith('.min.css')){
            const minified_css = new CleanCSS({}).minify(fs.readFileSync(fullpath).toString());
            css += minified_css.styles;
        }
        // otherwise, just concatenate the file
        else
            css += fs.readFileSync(path.join(__dirname, 'src', css_paths[i]));

        // add newlines between files
        css += '\n\n\n';
    }
    fs.writeFileSync(path.join(__dirname, 'dist', 'bundle.min.css'), css);

    // -----------------------------------------------
    // Bundle GUI core and merge all files into a single JS file
    // -----------------------------------------------
    let main_array = [];
    for(let i = 0; i < js_paths.length; i++){
        main_array.push(path.join(__dirname, 'src', js_paths[i]));
    }
    const webpack_opts = {
        ...(await BaseConfig({
            ...options,
            env: PUTER_ENV,
        })),
        mode: 'production',
        optimization: {
            minimize: true,
        },
    }
    console.log('webpack opts', webpack_opts);
    await webpack(webpack_opts, (err, stats) => {
        if(err){
            throw err;
            // console.error(err);
            // return;
        }
        //if(options?.verbose)
            console.log(stats.toString());
        // write to ./dist/bundle.min.js
        // fs.writeFileSync(path.join(__dirname, 'dist', 'bundle.min.js'), fs.readFileSync(path.join(__dirname, 'dist', 'main.js')));
        // remove ./dist/main.js
        // fs.unlinkSync(path.join(__dirname, 'dist', 'main.js'));
    });

    // Copy index.js to dist/gui.js
    // Prepend `window.gui_env="prod";` to `./dist/gui.js`
    fs.writeFileSync(
        path.join(__dirname, 'dist', 'gui.js'),
        `window.gui_env="${PUTER_ENV}"; \n\n` + fs.readFileSync(path.join(__dirname, 'src', 'index.js'))
    );

    const copy_these = [
        'images',
        'fonts',
        'favicons',
        'browserconfig.xml',
        'manifest.json',
        'favicon.ico',
        'security.txt',
    ];

    const recursive_copy = (src, dest) => {
        const stat = fs.statSync(src);
        if ( stat.isDirectory() ) {
            if( ! fs.existsSync(dest) ) fs.mkdirSync(dest);
            const files = fs.readdirSync(src);
            for ( const file of files ) {
                recursive_copy(path.join(src, file), path.join(dest, file));
            }
        } else {
            fs.copyFileSync(src, dest);
        }
    };

    for ( const to_copy of copy_these ) {
        recursive_copy(path.join(__dirname, 'src', to_copy), path.join(__dirname, 'dist', to_copy));
    }
}

/**
 * Generates the HTML content for the GUI based on the specified configuration options. The function
 * creates a new HTML document with the specified title, description, and other metadata. The function
 * also includes the necessary CSS and JavaScript files, as well as the required meta tags for social
 * media sharing and search engine optimization. The function is designed to be used in development
 * environments to generate the HTML content for the GUI.
 *
 * @param {Object} options - The configuration options for the GUI.
 * @param {string} options.env - The environment in which the GUI is running (e.g., "dev" or "prod").
 * @param {string} options.api_origin - The origin of the API server.
 * @param {string} options.title - The title of the GUI.
 * @param {string} options.company - The name of the company or organization.
 * @param {string} options.description - The description of the GUI.
 * @param {string} options.app_description - The description of the application.
 * @param {string} options.short_description - The short description of the GUI.
 * @param {string} options.origin - The origin of the GUI.
 * @param {string} options.social_card - The URL of the social media card image.
 * @returns {string} The HTML content for the GUI based on the specified configuration options.
 *
 */
function generateDevHtml(options){
    let start_t = Date.now();

    // final html string
    let h = '';

    h += `<!DOCTYPE html>`;
    h += `<html lang="en">`;

    h += `<head>`;
        h += `<title>${encode((options.title))}</title>`
        h += `<meta name="author" content="${encode(options.company)}">`
        // description
        let description = options.description;
        // if app_description is set, use that instead
        if(options.app_description){
            description = options.app_description;
        }
        // if no app_description is set, use short_description if set
        else if(options.short_description){
            description = options.short_description;
        }

        // description
        h += `<meta name="description" content="${encode((description).replace(/\n/g, " "))}">`
        // facebook domain verification
        h += `<meta name="facebook-domain-verification" content="e29w3hjbnnnypf4kzk2cewcdaxym1y" />`;
        // canonical url
        h += `<link rel="canonical" href="${options.origin}">`;

        // DEV: load every CSS file individually
        if(options.env === 'dev'){
            for(let i = 0; i < css_paths.length; i++){
                h += `<link rel="stylesheet" href="${css_paths[i]}">`;
            }
        }

        // Facebook meta tags
        h += `<meta property="og:url" content="https://${options.domain}">`;
        h += `<meta property="og:type" content="website">`;
        h += `<meta property="og:title" content="${encode(options.title)}">`;
        h += `<meta property="og:description" content="${encode((options.short_description).replace(/\n/g, " "))}">`;
        h += `<meta property="og:image" content="${options.social_card}">`;

        // Twitter meta tags
        h += `<meta name="twitter:card" content="summary_large_image">`;
        h += `<meta property="twitter:domain" content="${options.domain}">`;
        h += `<meta property="twitter:url" content="https://${options.domain}">`;
        h += `<meta name="twitter:title" content="${encode(options.title)}">`;
        h += `<meta name="twitter:description" content="${encode((options.short_description).replace(/\n/g, " "))}">`;
        h += `<meta name="twitter:image" content="${options.social_card}">`;

        // favicons
        h += `
        <link rel="apple-touch-icon" sizes="57x57" href="/favicons/apple-icon-57x57.png">
        <link rel="apple-touch-icon" sizes="60x60" href="/favicons/apple-icon-60x60.png">
        <link rel="apple-touch-icon" sizes="72x72" href="/favicons/apple-icon-72x72.png">
        <link rel="apple-touch-icon" sizes="76x76" href="/favicons/apple-icon-76x76.png">
        <link rel="apple-touch-icon" sizes="114x114" href="/favicons/apple-icon-114x114.png">
        <link rel="apple-touch-icon" sizes="120x120" href="/favicons/apple-icon-120x120.png">
        <link rel="apple-touch-icon" sizes="144x144" href="/favicons/apple-icon-144x144.png">
        <link rel="apple-touch-icon" sizes="152x152" href="/favicons/apple-icon-152x152.png">
        <link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-icon-180x180.png">
        <link rel="icon" type="image/png" sizes="192x192"  href="/favicons/android-icon-192x192.png">
        <link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png">
        <link rel="icon" type="image/png" sizes="96x96" href="/favicons/favicon-96x96.png">
        <link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png">
        <link rel="manifest" href="/manifest.json">
        <meta name="msapplication-TileColor" content="#ffffff">
        <meta name="msapplication-TileImage" content="/favicons/ms-icon-144x144.png">
        <meta name="theme-color" content="#ffffff">`;

        // preload images when applicable
        h += `<link rel="preload" as="image" href="./images/wallpaper.webp">`;
    h += `</head>`;

    h += `<body>`;

        // To indicate that the GUI is running to any 3rd-party scripts that may be running on the page
        // specifically, the `puter.js` script
        // This line is also present verbatim in `src/index.js` for production builds
        h += `<script>window.puter_gui_enabled = true;</script>`;

        // DEV: load every JS library individually
        if(options.env === 'dev'){
            for(let i = 0; i < lib_paths.length; i++){
                h += `<script src="${lib_paths[i]}"></script>`;
            }
        }

        // load images and icons as base64 for performance
        if(options.env === 'dev'){
            h += `<script>`;
                h += `window.icons = {};`
                fs.readdirSync(path.join(__dirname, 'src/icons')).forEach(file => {
                    // skip dotfiles
                    if(file.startsWith('.'))
                        return;
                    // load image
                    let buff = new Buffer.from(fs.readFileSync(path.join(__dirname, 'src/icons') + '/' + file));
                    // convert to base64
                    let base64data = buff.toString('base64');
                    // add to `window.icons`
                    if(file.endsWith('.png'))
                        h += `window.icons['${file}'] = "data:image/png;base64,${base64data}";\n`;
                    else if(file.endsWith('.svg'))
                        h += `window.icons['${file}'] = "data:image/svg+xml;base64,${base64data}";\n`;
                });
            h += `</script>`;
        }


        // PROD: gui.js
        if(options.env === 'prod'){
            h += `<script src="/dist/gui.js"></script>`;
        }
        // DEV: load every JS file individually
        else{
            for(let i = 0; i < js_paths.length; i++){
                h += `<script type="module" src="${js_paths[i]}"></script>`;
            }
            // load GUI
            h += `<script type="module" src="/index.js"></script>`;
        }

        // ----------------------------------------
        // Initialize GUI with config options
        // ----------------------------------------
        h += `
        <script type="text/javascript">
        window.addEventListener('load', function() {`
            h += `gui()`;
        h += `});
        </script>`;

    h += `</body>

    </html>`;

    console.log(`/index.js: ` + (Date.now() - start_t)/1000);
    return h;
}

// export
export { generateDevHtml, build };
