const path = require("path");
const env = require("./env.cjs");
const paths = require("./paths.cjs");
const { dependencies } = require("../package.json");

const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");

// GitHub base URL to use for production source maps
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
module.exports.sourceMapURL = () => {
  const ref = env.version().endsWith("dev")
    ? process.env.GITHUB_SHA || "dev"
    : env.version();
  return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}/`;
};

// Files from NPM Packages that should not be imported
module.exports.ignorePackages = () => [];

// Files from NPM packages that we should replace with empty file
module.exports.emptyPackages = ({ isLandingPageBuild }) =>
  [
    // Icons in landingpage conflict with icons in HA so we don't load.
    isLandingPageBuild &&
      require.resolve(
        path.resolve(paths.root_dir, "src/components/ha-icon.ts")
      ),
    isLandingPageBuild &&
      require.resolve(
        path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts")
      ),
  ].filter(Boolean);

module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
  __DEV__: !isProdBuild,
  __BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
  __VERSION__: JSON.stringify(env.version()),
  __DEMO__: false,
  __BACKWARDS_COMPAT__: false,
  __STATIC_PATH__: "/static/",
  __HASS_URL__: `\`${
    "HASS_URL" in process.env
      ? process.env.HASS_URL
      : // eslint-disable-next-line no-template-curly-in-string
        "${location.protocol}//${location.host}"
  }\``,
  "process.env.NODE_ENV": JSON.stringify(
    isProdBuild ? "production" : "development"
  ),
  ...defineOverlay,
});

module.exports.htmlMinifierOptions = {
  caseSensitive: true,
  collapseWhitespace: true,
  conservativeCollapse: true,
  decodeEntities: true,
  removeComments: true,
  removeRedundantAttributes: true,
  minifyCSS: {
    compatibility: "*,-properties.zeroUnits",
  },
};

module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
  safari10: !latestBuild,
  ecma: latestBuild ? 2015 : 5,
  module: latestBuild,
  format: { comments: false },
  sourceMap: !isTestBuild,
});

/** @type {import('@rspack/core').SwcLoaderOptions} */
module.exports.swcOptions = () => ({
  jsc: {
    loose: true,
    externalHelpers: true,
    target: "ES2021",
    parser: {
      syntax: "typescript",
      decorators: true,
    },
  },
});

module.exports.babelOptions = ({ latestBuild, isTestBuild, sw }) => ({
  babelrc: false,
  compact: false,
  assumptions: {
    privateFieldsAsProperties: true,
    setPublicClassFields: true,
    setSpreadProperties: true,
  },
  browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`,
  presets: [
    [
      "@babel/preset-env",
      {
        shippedProposals: true,
      },
    ],
  ],
  plugins: [
    // Inject Core-JS polyfills on demand. Babel 8 removed preset-env's
    // `useBuiltIns`/`corejs` options, so the equivalent polyfill provider is
    // configured directly here (`usage-global` matches the old `useBuiltIns: "usage"`).
    [
      "babel-plugin-polyfill-corejs3",
      {
        method: "usage-global",
        version: dependencies["core-js"],
        shippedProposals: true,
      },
    ],
    [
      path.join(BABEL_PLUGINS, "inline-constants-plugin.cjs"),
      {
        modules: ["@mdi/js"],
        ignoreModuleNotFound: true,
      },
    ],
    // Import helpers and regenerator from runtime package.
    // `moduleName` is pinned so helpers resolve from `@babel/runtime`: the
    // corejs3 polyfill provider above otherwise redirects them to the
    // (uninstalled) `@babel/runtime-corejs3`, which preset-env used to suppress
    // internally when it owned the polyfill injection via `useBuiltIns`.
    [
      "@babel/plugin-transform-runtime",
      { version: dependencies["@babel/runtime"], moduleName: "@babel/runtime" },
    ],
    "@babel/plugin-transform-class-properties",
    "@babel/plugin-transform-private-methods",
  ].filter(Boolean),
  exclude: [
    // \\ for Windows, / for Mac OS and Linux
    /node_modules[\\/]core-js/,
  ],
  sourceMaps: !isTestBuild,
  overrides: [
    {
      // Add plugin to inject various polyfills, excluding the polyfills
      // themselves to prevent self-injection.
      plugins: [
        [
          path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"),
          { method: "usage-global" },
        ],
      ],
      exclude: [
        path.join(paths.root_dir, "src/resources/polyfills"),
        ...[
          "@formatjs/(?:ecma402-abstract|intl-\\w+)",
          "@lit-labs/virtualizer/polyfills",
          "@webcomponents/scoped-custom-element-registry",
          "element-internals-polyfill",
          "proxy-polyfill",
          "unfetch",
        ].map((p) => new RegExp(`/node_modules/${p}/`)),
      ],
    },
    {
      // Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
      // Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
      // (otherwise babel-plugin-polyfill-corejs3 injects bare require("core-js/modules/...") calls
      // that rspack does not transform, causing ReferenceError in browsers like Safari 14).
      sourceType: "unambiguous",
      include: /\/node_modules\//,
      exclude: [
        "element-internals-polyfill",
        "@?lit(?:-labs|-element|-html)?",
        "@formatjs/(?:ecma402-abstract|intl-\\w+)",
      ].map((p) => new RegExp(`/node_modules/${p}/`)),
    },
  ],
});

const nameSuffix = (latestBuild) => (latestBuild ? "-modern" : "-legacy");

const outputPath = (outputRoot, latestBuild) =>
  path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");

const publicPath = (latestBuild, root = "") =>
  latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;

/*
  BundleConfig {
    // Object with entrypoints that need to be bundled
    entry: { [name: string]: pathToFile },
    // Folder where bundled files need to be written
    outputPath: string,
    // absolute url-path where bundled files can be found
    publicPath: string,
    // extra definitions that we need to replace in source
    defineOverlay: {[name: string]: value },
    // if this is a production build
    isProdBuild: boolean,
    // If we're targeting latest browsers
    latestBuild: boolean,
    // If we're doing a stats build (create nice chunk names)
    isStatsBuild: boolean,
    // If it's just a test build in CI, skip time on source map generation
    isTestBuild: boolean,
    // Names of entrypoints that should not be hashed
    dontHash: Set<string>
  }
  */

module.exports.config = {
  app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
    return {
      name: "frontend" + nameSuffix(latestBuild),
      entry: {
        "service-worker": !latestBuild
          ? {
              import: "./src/entrypoints/service-worker.ts",
              layer: "sw",
            }
          : "./src/entrypoints/service-worker.ts",
        app: "./src/entrypoints/app.ts",
        authorize: "./src/entrypoints/authorize.ts",
        onboarding: "./src/entrypoints/onboarding.ts",
        core: "./src/entrypoints/core.ts",
        "custom-panel": "./src/entrypoints/custom-panel.ts",
      },
      outputPath: outputPath(paths.app_output_root, latestBuild),
      publicPath: publicPath(latestBuild),
      isProdBuild,
      latestBuild,
      isStatsBuild,
      isTestBuild,
      isWDS,
    };
  },

  demo({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) {
    return {
      name: "demo" + nameSuffix(latestBuild),
      entry: {
        main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
      },
      outputPath: outputPath(paths.demo_output_root, latestBuild),
      publicPath: publicPath(latestBuild),
      defineOverlay: {
        __VERSION__: JSON.stringify(`DEMO-${env.version()}`),
        __DEMO__: true,
      },
      isProdBuild,
      latestBuild,
      isStatsBuild,
      isTestBuild,
    };
  },

  cast({ isProdBuild, latestBuild }) {
    const entry = {
      launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
      media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
    };

    if (latestBuild) {
      entry.receiver = path.resolve(
        paths.cast_dir,
        "src/receiver/entrypoint.ts"
      );
    }

    return {
      name: "cast" + nameSuffix(latestBuild),
      entry,
      outputPath: outputPath(paths.cast_output_root, latestBuild),
      publicPath: publicPath(latestBuild),
      isProdBuild,
      latestBuild,
      defineOverlay: {
        __BACKWARDS_COMPAT__: true,
      },
    };
  },

  gallery({ isProdBuild, latestBuild }) {
    return {
      name: "gallery" + nameSuffix(latestBuild),
      entry: {
        entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
      },
      outputPath: outputPath(paths.gallery_output_root, latestBuild),
      publicPath: publicPath(latestBuild),
      isProdBuild,
      latestBuild,
      defineOverlay: {
        __DEMO__: true,
      },
    };
  },

  landingPage({ isProdBuild, latestBuild }) {
    return {
      name: "landing-page" + nameSuffix(latestBuild),
      entry: {
        entrypoint: path.resolve(paths.landingPage_dir, "src/entrypoint.js"),
      },
      outputPath: outputPath(paths.landingPage_output_root, latestBuild),
      publicPath: publicPath(latestBuild),
      isProdBuild,
      latestBuild,
      isLandingPageBuild: true,
    };
  },

  e2eTestApp({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) {
    return {
      name: "e2e-test-app" + nameSuffix(latestBuild),
      entry: {
        dashboard: path.resolve(
          paths.e2eTestApp_dir,
          "src/dashboard-entrypoint.ts"
        ),
        main: path.resolve(paths.e2eTestApp_dir, "src/entrypoint.ts"),
        onboarding: path.resolve(
          paths.e2eTestApp_dir,
          "src/onboarding-entrypoint.ts"
        ),
      },
      outputPath: outputPath(paths.e2eTestApp_output_root, latestBuild),
      publicPath: publicPath(latestBuild),
      defineOverlay: {
        __VERSION__: JSON.stringify(`E2E-TEST-${env.version()}`),
        __DEMO__: true,
      },
      isProdBuild,
      latestBuild,
      isStatsBuild,
      isTestBuild,
    };
  },
};
