import fs from "fs/promises";
import { mkdir_to, exec_i } from "../lib/builderator.js";
import glob from "fast-glob";
import esbuild from "esbuild";
import sveltePlugin from "esbuild-svelte";
import path from 'path';
import { main as build_icons } from "./icons.js";
import { io } from "socket.io-client";

export const description = "Builds the Svelte app specified by --input and places the bundle in --output."

export const options = [
  ["--config <file>", "specify a build.json file", "build.json"],
  ["--watch", "have esbuild watch and rebuild"],
  ["--watch-pattern", "glob pattern for esbuild to watch file"],
  ["--log-level <level>", "esbuild log level"],
  ["--legal-comments", "how to handle copyright comments"],
];

const CWD = process.cwd();
const socket = io("ws://127.0.0.1:5001");

const dollarImport = () => ({
  name: "dollarImport",
  setup(build) {
    build.onResolve({ filter: /.*/ }, (args) => {
      if(args.path.startsWith("$")) {
        args.path = path.join(CWD, args.path.slice(1));

        return {
          path: args.path,
          external: false,
        }
      } else {
        return {};
      }
    })
  }
})

const buildIcons = (production) => ({
  name: "buildIcons",
  setup(build) {
    build.onStart(async () => {
      const config = {
        allIcons: "./static/icons/*.svg",
        spriteSheet: "./public/icon-sprites.svg",
        iconsIndex: "./public/icons/index.json",
        dontExit: true
      }

      if(production) {
        config.prodList = "./static/prod_icons.json";
      }

      await build_icons(config);
    });
  }
})

const syncContent = (watch_pattern) => ({
  name: "syncContent",
  setup(build) {
    build.onResolve({filter: /.*main\.js/}, (args) => {
      // when there's no watch pattern we don't tell esbuild to watch
      if(watch_pattern) {
        const same_path = path.join(CWD, args.path);
        const watch_files = glob.sync(path.join(CWD, watch_pattern));
        return {
          path: same_path,
          watchFiles: watch_files
        }
      } else {
        return undefined;
      }
    });

    build.onEnd(async (result) => {
      socket.emit("/reloader/notify", {});

      if(result.errors.length > 0) {
        console.error(`Build ended with ${result.errors.length} errors`);
      } else {
        if(process.platform === "win32") {
          try {
          exec_i("robocopy static\\ public\\ /e /NFL /NDL /NJH /NJS /nc /ns /np");
          } catch(error) {
            if(error.status === 16) {
              console.error(error);
            }
          }
        } else {
          exec_i("rsync -a static/ public/");
        }
      }
    });
  }
})

const saveMetaFile = (build_meta, error_file) => ({
  name: "saveMetaFile",
  setup(build) {
    build.onEnd(async (result) => {
      // when there's an error esbuild doesn't include the metafile portion
      if(result.metafile) {
        mkdir_to(build_meta);
        await fs.writeFile(build_meta, JSON.stringify(result.metafile, null, 4));
      }

      mkdir_to(error_file);
      await fs.writeFile(error_file, JSON.stringify(result, null, 4));
    });
  }
})

const devMode = () => ({
  name: 'devMode',
  setup(build) {
    const options = build.initialOptions;
    options.define = options.define || {};
    // esbuild 0.16 requires this to be a string, but changes it to code so this will be an actual boolean type in the code
    options.define['process.env.DANGER_ADMIN'] = options.minify ? "'0'" : "'1'";
  }
})

export const run_build = async (config, opts) => {
  let build_meta;
  let error_file;

  // assign the opts to the config
  Object.assign(config, opts);

  // tack on the plugins list for esbuild
  config.plugins = [
    devMode(),
    dollarImport(),
    sveltePlugin({ compilerOptions: { css: true } }),
    buildIcons(config.prod),
    syncContent(config.watchPattern),
  ];

  // make the directories if they don't exist
  mkdir_to(config.outfile);

  // HACK: esbuild is too strict about stray params so remove
  delete config.prod;
  delete config.watchPattern;

  // fix up the metafile since esbuild is weird about it
  if(typeof config.metafile === "string") {
    build_meta = config.metafile;
    error_file = config.errorFile;
    config.metafile = true;
    delete config.errorFile;
  }

  // add the plugin that saves the build results file if requested
  if(build_meta) {
    config.plugins.push(saveMetaFile(build_meta, error_file));
  }

  // when using JS to build, it returns the Object rather than write JSON out
  let result;

  try {
    result = await esbuild.build(config);
  } catch(error) {
    // in this case, there is no result because of an error
    result = error;
  }

  return result.errors.length;
}

export const main = async (opts) => {
  const config = JSON.parse(await fs.readFile(opts.config));
  let error_count = 0;
  // remove this since esbuild doesn't support it
  delete opts.config;

  for(let i = 0; i < config.length; i++) {
    const build = config[i];
    // BUG: I'd like this to be not-sequential but that's too hard to make reliable right now
    error_count += await run_build(build, opts);
  }

  if(error_count > 0) {
    console.error(`Build finished with ${error_count} errors.`);
  }

  if(!opts.watch) {
    process.exit(error_count > 0 ? 1 : 0);
  }
}
