const path = require('path');
const fs = require('fs');

function toPascalCase(string) {
  return `${string}`
    .toLowerCase()
    .replace(new RegExp(/[-_]+/, 'g'), ' ')
    .replace(new RegExp(/[^\w\s]/, 'g'), '')
    .replace(new RegExp(/\s+(.)(\w*)/, 'g'), ($1, $2, $3) => `${$2.toUpperCase() + $3}`)
    .replace(new RegExp(/\w/), (s) => s.toUpperCase());
}

const PACKAGE_PATH = require('./../package.json').name;
const APP_NAME = PACKAGE_PATH.split('/').pop().replace('-dev', '').replace('-', '');
const PLUGIN_NAME = `${toPascalCase(APP_NAME)}WebpackPlugin`;
const PACKAGE_ENTRY_NAME = `${APP_NAME}.js`;
const DEFAULT_OUTPUT_FOLDER = APP_NAME;
const DEFAULT_MODULES_PATH = `node_modules`;
const ESM_PACKAGE_ENTRY_NAME = `${APP_NAME}-esm.js`;
const ENVIRONMENT_VARIABLE = `${toPascalCase(APP_NAME)}Environment`;

function normalizeWebpackOutputFolder(folder) {
  return String(folder).replace(/^\/+|\/+$/g, '');
}

function buildWebpackInjectedPublicPath(publicPath, outputFolderBare) {
  const pp = (publicPath || '').trim();
  const f = normalizeWebpackOutputFolder(outputFolderBare);
  if (!pp) {
    return `/${f}/`;
  }
  if (/^https?:\/\//i.test(pp)) {
    const base = pp.endsWith('/') ? pp : `${pp}/`;
    return new URL(`${f}/`, base).href;
  }
  return `${pp.replace(/\/+$/, '')}/${f}/`;
}

const IGNORED_PATTERNS = [
  /.*\.npmignore/,
  /.*\.gitignore/,
  /.*\.md$/,
  /.*\.ts$/,
  /.*\.d\.ts$/,
  /.*\.json$/,
  /.*\.ico$/,
  /.*\.pot$/,
  /^.*(?<!iframe\..{1,})\.html$/,
  /vite\\?.*$/,
  /webpack\\?.*$/,
  /react\\?.*$/,
  /robots.txt$/,
  /main\..*\.js$/,
  /.*\\sandbox-eblock.*$/,
];

class WebpackPlugin {
  _outputFolder = DEFAULT_OUTPUT_FOLDER;
  _modulesPath = DEFAULT_MODULES_PATH;
  _publicPath = undefined;
  _sources = null;
  constructor(options = {}) {
    if (options.outputFolder !== undefined) {
      this._outputFolder = normalizeWebpackOutputFolder(options.outputFolder);
    } else {
      this._outputFolder = normalizeWebpackOutputFolder(DEFAULT_OUTPUT_FOLDER);
    }
    if (options.modulesPath !== undefined) {
      this._modulesPath = options.modulesPath;
    }
    if (options.publicPath !== undefined) {
      this._publicPath = options.publicPath;
    }
  }

  addAsset = async (compilation, sourcePath, destPath) => {
    const filePath = path.resolve(compilation.compiler.context, sourcePath);
    if (IGNORED_PATTERNS.some((pattern) => pattern.test(filePath))) {
      return;
    }
    if (!compilation.getAsset(destPath)) {
      const [stat, data] = await Promise.all([fs.promises.stat(filePath), fs.promises.readFile(filePath)]);
      const { size } = stat;
      const sources = this._sources || compilation.compiler.webpack.sources;
      const source = new sources.RawSource(data);
      compilation.emitAsset(destPath, source, { size });
    }
  };

  copyFiles = async (compilation, source, destination) => {
    const directoryEntries = await fs.promises.readdir(source, { withFileTypes: true });
    // console.log(destination);
    // await fs.promises.mkdir(destination, { recursive: true });
    return Promise.all(
      directoryEntries.map(async (entry) => {
        const sourcePath = path.join(source, entry.name);
        const destinationPath = path.join(destination, entry.name);
        return entry.isDirectory() ?
            this.copyFiles(compilation, sourcePath, destinationPath)
          : this.addAsset(compilation, sourcePath, destinationPath);
      }),
    );
  };

  apply(compiler) {
    const optionsPublicPath = compiler.options.output.publicPath || '';
    const publicPath = this._publicPath === undefined ? optionsPublicPath : this._publicPath;
    const injected = buildWebpackInjectedPublicPath(publicPath, this._outputFolder);
    const initFragment = `var ${ENVIRONMENT_VARIABLE} = { publicPath: ${JSON.stringify(injected)} };`;
    const webpack = compiler.webpack;
    const javascript = webpack.javascript;
    this._sources = webpack.sources;
    compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
      compilation.hooks.processAssets.tapAsync(
        {
          name: PLUGIN_NAME,
          stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
        },
        (_unusedAssets, cb) =>
          this.copyFiles(compilation, path.join(this._modulesPath, PACKAGE_PATH), this._outputFolder).then(
            () => cb(null),
            (e) => cb(e),
          ),
      );
      const hooks = javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
      hooks.renderModuleContent.tap(PLUGIN_NAME, (source, module) => {
        const name = module && module.nameForCondition();
        if (
          typeof name === 'string' &&
          (name.indexOf(PACKAGE_ENTRY_NAME) >= 0 || name.indexOf(ESM_PACKAGE_ENTRY_NAME) >= 0)
        ) {
          const sources = this._sources || compilation.compiler.webpack.sources;
          return new sources.ConcatSource(initFragment, source);
        }
        return source;
      });
    });
  }
}

module.exports = WebpackPlugin;
