#!/usr/bin/env ../../node_modules/.bin/ts-node

import fs from 'fs-extra';
import path, { dirname, join, relative } from 'path';
import { build } from 'tsup';
import aliasPlugin from 'esbuild-plugin-alias';
import dedent from 'ts-dedent';
import slash from 'slash';
import { exec } from '../utils/exec';

const hasFlag = (flags: string[], name: string) => !!flags.find((s) => s.startsWith(`--${name}`));

const run = async ({ cwd, flags }: { cwd: string; flags: string[] }) => {
  const {
    name,
    dependencies,
    peerDependencies,
    bundler: { entries = [], untypedEntries = [], platform, pre, post },
  } = await fs.readJson(join(cwd, 'package.json'));

  if (pre) {
    await exec(`node -r ${__dirname}/../node_modules/esbuild-register/register.js ${pre}`, { cwd });
  }

  const reset = hasFlag(flags, 'reset');
  const watch = hasFlag(flags, 'watch');
  const optimized = hasFlag(flags, 'optimized');

  if (reset) {
    await fs.emptyDir(join(process.cwd(), 'dist'));
  }

  const tsConfigPath = join(cwd, 'tsconfig.json');
  const tsConfigExists = await fs.pathExists(tsConfigPath);

  await Promise.all([
    // SHIM DTS FILES (only for development)
    ...(optimized
      ? []
      : entries.map(async (file: string) => {
          const { name: entryName, dir } = path.parse(file);

          const pathName = join(process.cwd(), dir.replace('./src', 'dist'), `${entryName}.d.ts`);
          const srcName = join(process.cwd(), file);

          const rel = relative(dirname(pathName), dirname(srcName))
            .split(path.sep)
            .join(path.posix.sep);

          await fs.ensureFile(pathName);
          await fs.writeFile(
            pathName,
            dedent`
              // dev-mode
              export * from '${rel}/${entryName}';
            `
          );
        })),

    // BROWSER EMS
    build({
      silent: true,
      entry: [...entries, ...untypedEntries].map((e: string) => slash(join(cwd, e))),
      watch,
      ...(tsConfigExists ? { tsconfig: tsConfigPath } : {}),
      outDir: join(process.cwd(), 'dist'),
      format: ['esm'],
      target: 'chrome100',
      clean: !watch,
      platform: platform || 'browser',
      esbuildPlugins: [
        aliasPlugin({
          process: path.resolve('../node_modules/process/browser.js'),
          util: path.resolve('../node_modules/util/util.js'),
        }),
      ],
      external: [name, ...Object.keys(dependencies || {}), ...Object.keys(peerDependencies || {})],

      dts:
        optimized && tsConfigExists
          ? {
              entry: entries,
              resolve: true,
            }
          : false,
      esbuildOptions: (c) => {
        /* eslint-disable no-param-reassign */
        c.conditions = ['module'];
        c.logLevel = 'error';
        c.platform = platform || 'browser';
        c.legalComments = 'none';
        c.minifyWhitespace = optimized;
        c.minifyIdentifiers = false;
        c.minifySyntax = optimized;
        /* eslint-enable no-param-reassign */
      },
    }),

    // NODE CJS
    build({
      silent: true,
      entry: [...entries, ...untypedEntries].map((e: string) => slash(join(cwd, e))),
      watch,
      outDir: join(process.cwd(), 'dist'),
      ...(tsConfigExists ? { tsconfig: tsConfigPath } : {}),
      format: ['cjs'],
      target: 'node16',
      platform: 'node',
      clean: !watch,
      external: [name, ...Object.keys(dependencies || {}), ...Object.keys(peerDependencies || {})],

      esbuildOptions: (c) => {
        /* eslint-disable no-param-reassign */
        c.logLevel = 'error';
        c.platform = 'node';
        c.legalComments = 'none';
        c.minifyWhitespace = optimized;
        c.minifyIdentifiers = optimized;
        c.minifySyntax = optimized;
        /* eslint-enable no-param-reassign */
      },
    }),
  ]);

  if (post) {
    await exec(
      `node -r ${__dirname}/../node_modules/esbuild-register/register.js ${post}`,
      { cwd },
      { debug: true }
    );
  }
};

const flags = process.argv.slice(2);
const cwd = process.cwd();

run({ cwd, flags }).catch((err: unknown) => {
  // We can't let the stack try to print, it crashes in a way that sets the exit code to 0.
  // Seems to have something to do with running JSON.parse() on binary / base64 encoded sourcemaps
  // in @cspotcode/source-map-support
  if (err instanceof Error) {
    console.error(err.stack);
  }
  process.exit(1);
});