const path = require('path');
const fs = require('fs');
const MIME_TYPES = require('./mime-types');
const { toPascalCase, cleanUrl, normalizePath, copyFolder } = require('./utils');

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

/** Strip leading/trailing slashes so the output folder is a single pathname segment. */
function normalizeOutputFolderSegment(outputFolder) {
  return String(outputFolder ?? '').replace(/^\/+|\/+$/g, '');
}

/**
 * Build the runtime publicPath string for inject. Do not use path.posix.join with http(s) URLs — it breaks https://.
 */
function buildInjectedPublicPath(publicPath, outputFolderOption) {
  const raw = outputFolderOption !== undefined && outputFolderOption !== null && outputFolderOption !== ''
    ? String(outputFolderOption)
    : DEFAULT_OUTPUT_FOLDER;
  const folder = normalizeOutputFolderSegment(raw);
  const base = (publicPath || '').trim();

  if (!base) {
    return `/${folder}/`;
  }

  if (/^https?:\/\//i.test(base)) {
    const baseUrl = base.endsWith('/') ? base : `${base}/`;
    return new URL(`${folder}/`, baseUrl).href;
  }

  const normalized = `${base.replace(/\/+$/, '')}/${folder}/`;
  return normalized;
}

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

const ViteServePlugin = (options) => {
  const rootPath = process.cwd();
  const destPath = path.posix.join(rootPath, options.outputFolder || DEFAULT_OUTPUT_FOLDER);
  return {
    name: PLUGIN_NAME,
    apply: 'serve',

    transform: (code, id) => {
      if (code.indexOf(PACKAGE_PATH) < 0) {
        return code;
      }
      const dir = buildInjectedPublicPath(options.publicPath || '', options.outputFolder);
      return `var ${ENVIRONMENT_VARIABLE}= { publicPath: ${JSON.stringify(dir)} }; ${code}`;
    },

    configureServer: (server) => {
      server.middlewares.use('/', async (req, res, next) => {
        if (!req.url) {
          next();
          return;
        }
        const absReqPath = path.posix.join(rootPath, cleanUrl(req.url));
        if (absReqPath.startsWith(destPath)) {
          const sourcePath = path.posix.join(
            options.modulesPath || DEFAULT_MODULES_PATH,
            PACKAGE_PATH,
            absReqPath.replace(destPath, ''),
          );
          if (!IGNORED_PATTERNS.some((pattern) => pattern.test(sourcePath)) && fs.existsSync(sourcePath)) {
            await sendContentByFilePath(sourcePath);
            return;
          }
        }
        next();

        async function sendContentByFilePath(filePath) {
          const handledPath = normalizePath(filePath);
          if (!fs.existsSync(handledPath)) {
            next();
            return;
          }
          if (!fs.statSync(handledPath).isFile()) {
            next();
            return;
          }
          const content = await fs.promises.readFile(handledPath);
          const filePathParts = filePath.split('.');
          const ext = filePathParts.length > 1 ? filePathParts[filePathParts.length - 1] : '';
          res.writeHead(200, {
            'Content-Type': (ext && MIME_TYPES[ext]) || 'text/plain',
          });
          res.end(content);
        }
      });
    },
  };
};

const ViteBuildPlugin = (options) => {
  const publicPath = options.publicPath || '';
  const sourcePath = path.posix.join(options.modulesPath || DEFAULT_MODULES_PATH, PACKAGE_PATH);
  let resolvedConfig = undefined;
  return {
    name: PLUGIN_NAME,
    apply: 'build',
    transform: (code, id) => {
      id = cleanUrl(id);
      if (id.endsWith(PACKAGE_ENTRY_NAME) || id.endsWith(ESM_PACKAGE_ENTRY_NAME)) {
        const dir = buildInjectedPublicPath(publicPath, options.outputFolder);
        return `let ${ENVIRONMENT_VARIABLE}= { publicPath: ${JSON.stringify(dir)} }; ${code}`;
      } else {
        return code;
      }
    },
    configResolved(config) {
      resolvedConfig = config;
    },
    async closeBundle() {
      const outDir = path.posix.join(resolvedConfig.root, resolvedConfig.build.outDir);
      const destPath = path.posix.join(outDir, options.outputFolder || DEFAULT_OUTPUT_FOLDER);

      if (!fs.existsSync(sourcePath)) {
        return;
      }

      const stat = await fs.promises.stat(sourcePath);

      if (stat.isDirectory()) {
        await copyFolder(sourcePath, destPath, (sourcePath) => {
          return !IGNORED_PATTERNS.some((pattern) => pattern.test(sourcePath));
        });
      }
    },
  };
};

const VitePlugin = (options = {}) => {
  return [ViteServePlugin(options), ViteBuildPlugin(options)];
};

module.exports = VitePlugin;
