/** Builds the userscript using esbuild.
 * This will:
 * 1. Update the package version across the entire project
 * 2. Bundle the JS files into one file (esbuild)
 * 3. Bundle the CSS files into one file (esbuild)
 * 4. Compress & obfuscate the bundled JS file (terner)
 * 5. Runs the CSS selector mangler (cssMandler.js)
 * @since 0.0.6
*/

// ES Module imports
import esbuild from 'esbuild';
import fs from 'fs';
import { execSync } from 'child_process';
import { consoleStyle } from './utils.js';
import mangleSelectors from './cssMangler.js';

import { createRequire } from 'module';
const require = createRequire(import.meta.url);
// CommonJS imports (require)
const terser = require('terser');

const isGitHub = !!process.env?.GITHUB_ACTIONS; // Is this running in a GitHub Action Workflow?'

console.log(`${consoleStyle.BLUE}Starting build...${consoleStyle.RESET}`);

// Tries to build the wiki if build.js is run in a GitHub Workflow
// if (isGitHub) {
//   try {
//     console.log(`Generating JSDoc...`);
//     execSync(`npx jsdoc src/ -r -d docs -t node_modules/minami`, { stdio: "inherit" });
//     console.log(`JSDoc built ${consoleStyle.GREEN}successfully${consoleStyle.RESET}`);
//   } catch (error) {
//     console.error(`${consoleStyle.RED + consoleStyle.BOLD}Failed to generate JSDoc${consoleStyle.RESET}:`, error);
//     process.exit(1);
//   }
// }

console.log(`${consoleStyle.BLUE}Building 1 of 3...${consoleStyle.RESET}`);

// Tries to bump the version
try {
  const update = execSync('node build/update-version.js', { stdio: 'inherit' });
  console.log(`Version updated in meta file ${consoleStyle.GREEN}successfully${consoleStyle.RESET}`);
} catch (error) {
  console.error(`${consoleStyle.RED + consoleStyle.BOLD}Failed to update version number${consoleStyle.RESET}:`, error);
  process.exit(1);
}

// Fetches the userscript metadata banner
const metaContent = fs.readFileSync('src/BlueMarble.meta.js', 'utf8');

// Generates a new CSS file that imports all other CSS files inside the `src/` folder
const mainCSSText = fs.readdirSync('src/')
  .filter(file => file.endsWith('.css') && file != 'main.css')
  .map(file => `@import './${file}';`)
  .join(`\n`);

const mainCSSBanner = `/* This file was automatically generated by './build/build.js'.\n * All changes to this file will be lost!\n * @since 0.88.358\n*/\n\n`;

fs.writeFileSync('src/main.css', mainCSSBanner + mainCSSText + `\n`, 'utf8');

// Compiles the CSS files
esbuild.build({
  entryPoints: ['src/main.css'],
  bundle: true,
  outfile: 'dist/BlueMarble.user.css',
  minify: true
});

// Compiles the JS files
const resultEsbuild = await esbuild.build({
  entryPoints: ['src/main.js'], // "Infect" the files from this point (it spreads from this "patient 0")
  bundle: true, // Should the code be bundled?
  outfile: 'dist/BlueMarble.user.js', // The file the bundled code is exported to
  format: 'iife', // What format the bundler bundles the code into
  target: 'es2020', // What is the minimum version/year that should be supported? When omited, it attempts to support backwards compatability with legacy browsers
  platform: 'browser', // The platform the bundled code will be operating on
  legalComments: 'inline', // What level of legal comments are preserved? (Hard: none, Soft: inline)
  minify: false, // Should the code be minified?
  write: false, // Should we write the outfile to the disk?
}).catch(() => process.exit(1));

// Retrieves the JS file
const resultEsbuildJS = resultEsbuild.outputFiles.find(file => file.path.endsWith('.js'));

// Obfuscates the JS file
let resultTerser = await terser.minify(resultEsbuildJS.text, {
  mangle: {
    // toplevel: true, // Should globally exposed variables, functions, classes, etc. be obfuscated?
    keep_classnames: false, // Should class names be preserved?
    keep_fnames: false, // Should function names be preserved?
    reserved: [], // List of keywords to preserve
    properties: {
      // regex: /.*/, // Yes, I am aware I should be using a RegEx. Yes, like you, I am also suprised the userscript still functions
      keep_quoted: true, // Should names in quotes be preserved?
      reserved: ['willReadFrequently'] // What properties should be preserved?
    },
  },
  format: {
    comments: 'some' // Save legal comments
  },
  compress: {
    // toplevel: true, // Should unused globally exposed variables, functions, classes, etc. be removed if unused *by Blue Marble*?
    ecma: 2020, // Minimum supported ECMAScript version as release year. Note: versions before 2015 should pass in '5'
    dead_code: isGitHub, // Should unreachable code be removed?
    drop_console: isGitHub, // Should console code be removed?
    drop_debugger: isGitHub, // SHould debugger code be removed?
    passes: 2 // How many times terser will compress the code
  }
});

// Writes the obfuscated/mangled JS code to a file
fs.writeFileSync('dist/BlueMarble.user.js', resultTerser.code, 'utf8');

let importedMapCSS = {}; // The imported CSS map

// Only import a CSS map if we are NOT in production (GitHub Workflow)
// Theoretically, if the previous map is always imported, the names would not scramble. However, the names would never decrease in number...
if (!isGitHub) {
  try {
    importedMapCSS = JSON.parse(fs.readFileSync('dist/BlueMarble.user.css.map.json', 'utf8'));
  } catch {
    console.log(`${consoleStyle.YELLOW}Warning! Could not find a CSS map to import. A 100% new CSS map will be generated...${consoleStyle.RESET}`);
  }
}

// Mangles the CSS selectors
// If we are in production (GitHub Workflow), then generate the CSS mapping
const mapCSS = mangleSelectors({
  inputPrefix: 'bm-',
  outputPrefix: 'bm-',
  pathJS: 'dist/BlueMarble.user.js',
  pathCSS: 'dist/BlueMarble.user.css',
  importMap: importedMapCSS,
  returnMap: isGitHub
});

// If a map was returned, write it to the file
if (mapCSS) {
  fs.writeFileSync('dist/BlueMarble.user.css.map.json', JSON.stringify(mapCSS, null, 2));
}

// Adds the banner
fs.writeFileSync(
  'dist/BlueMarble.user.js', 
  metaContent + fs.readFileSync('dist/BlueMarble.user.js', 'utf8'), 
  'utf8'
);

console.log(`${consoleStyle.BLUE}Building 2 of 3...${consoleStyle.RESET}`);

const standaloneName = 'BlueMarble-Standalone'; // Standalone flavor name of flie
const standaloneBMUpdateURL = `https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/${standaloneName}.user.js`;

// Fetches the completed, main Blue Marble userscript files
const mainBMjs = fs.readFileSync('dist/BlueMarble.user.js', 'utf8');
let mainBMcss = fs.readFileSync('dist/BlueMarble.user.css', 'utf8');

// Removes new lines from the CSS file
mainBMcss = mainBMcss.replace(/\r?\n/g, '').trim();

// Injects the CSS into the Blue Marble JavaScript
let standaloneBMjs = mainBMjs.replace('GM_getResourceText("CSS-BM-File")', `\`${mainBMcss}\``);

// Removes the metadata in the header that points to the old CSS location
standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@resource\s+CSS-BM-File.*\r?\n?/g, '');

// Obtains the Roboto Mono font to inject
const robotoMonoLatin = fs.readFileSync('build/assets/RobotoMonoLatin.woff2');
const robotoMonoLatinBase64 = robotoMonoLatin.toString('base64');
const fontfaces = `@font-face{font-family:'Roboto Mono';font-style:normal;font-weight:400;src:url(data:font/woff2;base64,${robotoMonoLatinBase64})format('woff2');}`;

// Injects Roboto Mono into the JavaScript file
standaloneBMjs = standaloneBMjs.replace(/robotoMonoInjectionPoint[^'"]*/g, fontfaces);

// Obtains the Favicon to inject
const favicon = fs.readFileSync('dist/assets/Favicon.png');
const faviconBase64DataURI = `data:image/png;base64,${favicon.toString('base64')}`;

// Replaces the 2 different types of icon requests with base64
standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@icon\s+https.*\r?\n?/g, `// @icon64          ${faviconBase64DataURI}\n`);
standaloneBMjs = standaloneBMjs.replace(/https[^'"]+dist\/assets\/Favicon\.png[^'"]*/gi, faviconBase64DataURI);

// Updates the update/download URLs
standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@updateURL\s+https.*\r?\n?/g, `// @updateURL       ${standaloneBMUpdateURL}\n`);
standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@downloadURL\s+https.*\r?\n?/g, `// @downloadURL     ${standaloneBMUpdateURL}\n`);

// Generates the Blue Marble JS file that contains all external resources
fs.writeFileSync(`dist/${standaloneName}.user.js`, standaloneBMjs, 'utf-8');

console.log(`${consoleStyle.BLUE}Building 3 of 3...${consoleStyle.RESET}`);

const greasyForkName = 'BlueMarble-For-GreasyFork'; // GreasyFork flavor name of file
const greasyForkUpdateURL = `https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/${greasyForkName}.user.js`;

let greasyForkBMjs = metaContent + resultEsbuildJS.text; // Gets the unobfuscated code and adds the metadata banner

// Updates the name of the CSS location
greasyForkBMjs = greasyForkBMjs.replace(/(.*\/\/\s+\@resource\s+CSS-BM-File.*)(BlueMarble\.user\.css)(.*)/gi, `$1${greasyForkName}.user.css$3`);
// Don't use the multiline flag or everything will break!

// Updates the update/download URLs
greasyForkBMjs = greasyForkBMjs.replace(/\/\/\s+\@updateURL\s+https.*\r?\n?/g, `// @updateURL       ${greasyForkUpdateURL}\n`);
greasyForkBMjs = greasyForkBMjs.replace(/\/\/\s+\@downloadURL\s+https.*\r?\n?/g, `// @downloadURL     ${greasyForkUpdateURL}\n`);

// Bundles the CSS files without minification
esbuild.build({
  entryPoints: ['src/main.css'],
  bundle: true,
  outfile: `dist/${greasyForkName}.user.css`,
  minify: false
});

fs.writeFileSync(`dist/${greasyForkName}.user.js`, greasyForkBMjs, 'utf-8');

console.log(`${consoleStyle.GREEN + consoleStyle.BOLD + consoleStyle.UNDERLINE}Building complete!${consoleStyle.RESET}`);
