import path from 'path';
import { fs, createDebugger } from '@modern-js/utils';
import { Middleware } from 'koa';
import calculateEtag from 'etag';
import { IAppContext, NormalizedConfig } from '@modern-js/core';
import convertSourceMap from 'convert-source-map';
import { isCSSRequest, isJsRequest, isAssetRequest, cleanUrl } from '../utils';
import { PluginContainer } from '../plugins/container';
import { urlToModules, createAssetModule } from '../AssetModule';

const debug = createDebugger('esm:transform');

export const transformMiddleware = (
  config: NormalizedConfig,
  appContext: IAppContext,
  pluginContainer: PluginContainer,
): Middleware => {
  const { appDirectory } = appContext;

  return async (ctx, next) => {
    const { url, path: urlPath } = ctx;
    if (isJsRequest(url) || isCSSRequest(url) || isAssetRequest(url)) {
      // set content-type header
      ctx.type = 'application/javascript;charset=utf-8';

      let assetModule = urlToModules.get(cleanUrl(url));

      const ifNoneMatch = ctx.headers['if-none-match'];

      if (assetModule?.transformed) {
        if (ifNoneMatch && assetModule.etag === ifNoneMatch) {
          ctx.status = 304;
          ctx.body = '';
        } else {
          ctx.body = assetModule.transformed;
        }
        return;
      }

      const resolved = await pluginContainer.resolveId(urlPath);

      const filePath =
        typeof resolved === 'object' ? resolved?.id || urlPath : resolved;

      if (!filePath) {
        throw new Error(`Can't resolve ${urlPath}`);
      }

      debug(`resolved url ${url} to ${filePath || ''}`);

      assetModule = createAssetModule(url);
      assetModule.filePath = filePath;

      const relativeUrl = path.relative(appDirectory, filePath);

      assetModule.id = relativeUrl.startsWith('.')
        ? filePath
        : `/${relativeUrl}`;

      const result = filePath && (await pluginContainer.load(filePath));

      let code = typeof result === 'object' ? result?.code : result;

      if (!code) {
        code = fs.readFileSync(filePath, 'utf8');
      }

      // eslint-disable-next-line prefer-const
      let { code: transformed, map } = (await pluginContainer.transform(
        code,
        filePath,
      )) || { code };

      if (map) {
        const mapComment = convertSourceMap.fromObject(map).toComment();
        transformed = `${transformed}\n${mapComment}`;
      }

      const etag = calculateEtag(transformed || code);

      assetModule.transformed = transformed;
      assetModule.etag = etag;

      ctx.response.etag = etag;

      ctx.body = transformed;
    } else {
      return next();
    }
  };
};
