import {
  updateJson,
  updateProjectConfig,
  cleanupProject,
  newProject,
  runCLI,
  tmpProjPath,
  runCommand,
  createFile,
  uniq,
  getPackageManagerCommand,
  readJson,
  updateFile,
} from '@nx/e2e/utils';
import { join } from 'path';
import { ensureDirSync } from 'fs-extra';

describe('bundling libs', () => {
  let scope: string;

  beforeEach(() => {
    scope = newProject();
  });

  afterEach(() => cleanupProject());

  it('should support esbuild, rollup, vite bundlers for building libs', () => {
    const esbuildLib = uniq('esbuildlib');
    const viteLib = uniq('vitelib');
    const rollupLib = uniq('rolluplib');

    runCLI(
      `generate @nx/js:lib ${esbuildLib} --bundler=esbuild --no-interactive`
    );
    runCLI(`generate @nx/js:lib ${viteLib} --bundler=vite --no-interactive`);
    runCLI(
      `generate @nx/js:lib ${rollupLib} --bundler=rollup --no-interactive`
    );

    runCLI(`build ${esbuildLib}`);
    runCLI(`build ${viteLib}`);
    runCLI(`build ${rollupLib}`);

    const pmc = getPackageManagerCommand();
    let output: string;

    // Make sure outputs in commonjs project
    createFile(
      'test-cjs/package.json',
      JSON.stringify(
        {
          name: 'test-cjs',
          private: true,
          type: 'commonjs',
          dependencies: {
            [`@proj/${esbuildLib}`]: `file:../dist/libs/${esbuildLib}`,
            [`@proj/${viteLib}`]: `file:../dist/libs/${viteLib}`,
            [`@proj/${rollupLib}`]: `file:../dist/libs/${rollupLib}`,
          },
        },
        null,
        2
      )
    );
    createFile(
      'test-cjs/index.js',
      `
        const { ${esbuildLib} } = require('@proj/${esbuildLib}');
        const { ${viteLib} } = require('@proj/${viteLib}');
        const { ${rollupLib} } = require('@proj/${rollupLib}');
        console.log(${esbuildLib}());
        console.log(${viteLib}());
        console.log(${rollupLib}());
      `
    );
    runCommand(pmc.install, {
      cwd: join(tmpProjPath(), 'test-cjs'),
    });
    output = runCommand('node index.js', {
      cwd: join(tmpProjPath(), 'test-cjs'),
    });
    expect(output).toContain(esbuildLib);
    expect(output).toContain(viteLib);
    expect(output).toContain(rollupLib);

    // Make sure outputs in esm project
    createFile(
      'test-esm/package.json',
      JSON.stringify(
        {
          name: 'test-esm',
          private: true,
          type: 'module',
          dependencies: {
            [`@proj/${esbuildLib}`]: `file:../dist/libs/${esbuildLib}`,
            [`@proj/${viteLib}`]: `file:../dist/libs/${viteLib}`,
            [`@proj/${rollupLib}`]: `file:../dist/libs/${rollupLib}`,
          },
        },
        null,
        2
      )
    );
    createFile(
      'test-esm/index.js',
      `
        import { ${esbuildLib} } from '@proj/${esbuildLib}';
        import { ${viteLib} } from '@proj/${viteLib}';
        import { ${rollupLib} } from '@proj/${rollupLib}';
        console.log(${esbuildLib}());
        console.log(${viteLib}());
        console.log(${rollupLib}());
      `
    );
    runCommand(pmc.install, {
      cwd: join(tmpProjPath(), 'test-esm'),
    });
    output = runCommand('node index.js', {
      cwd: join(tmpProjPath(), 'test-esm'),
    });
    expect(output).toContain(esbuildLib);
    expect(output).toContain(viteLib);
    expect(output).toContain(rollupLib);
  }, 500_000);

  it('should support tsc and swc for building libs', async () => {
    const tscLib = uniq('tsclib');
    const swcLib = uniq('swclib');
    const tscEsmLib = uniq('tscesmlib');
    const swcEsmLib = uniq('swcesmlib');

    runCLI(`generate @nx/js:lib ${tscLib} --bundler=tsc --no-interactive`);
    runCLI(`generate @nx/js:lib ${swcLib} --bundler=swc --no-interactive`);
    runCLI(`generate @nx/js:lib ${tscEsmLib} --bundler=tsc --no-interactive`);
    runCLI(`generate @nx/js:lib ${swcEsmLib} --bundler=swc --no-interactive`);

    // Change module format to ESM
    updateJson(`libs/${tscEsmLib}/tsconfig.json`, (json) => {
      json.compilerOptions.module = 'esnext';
      return json;
    });
    updateJson(`libs/${swcEsmLib}/.swcrc`, (json) => {
      json.module.type = 'es6';
      return json;
    });
    // Node ESM requires file extensions in imports so must add them before building
    updateFile(
      `libs/${tscEsmLib}/src/index.ts`,
      `export * from './lib/${tscEsmLib}.js';`
    );
    updateFile(
      `libs/${swcEsmLib}/src/index.ts`,
      `export * from './lib/${swcEsmLib}.js';`
    );

    // Add additional entry points for `exports` field
    await updateProjectConfig(tscLib, (json) => {
      json.targets.build.options.additionalEntryPoints = [
        `libs/${tscLib}/src/foo/*.ts`,
      ];
      return json;
    });
    updateFile(`libs/${tscLib}/src/foo/bar.ts`, `export const bar = 'bar';`);
    updateFile(`libs/${tscLib}/src/foo/faz.ts`, `export const faz = 'faz';`);
    await updateProjectConfig(swcLib, (json) => {
      json.targets.build.options.additionalEntryPoints = [
        `libs/${swcLib}/src/foo/*.ts`,
      ];
      return json;
    });
    updateFile(`libs/${swcLib}/src/foo/bar.ts`, `export const bar = 'bar';`);
    updateFile(`libs/${swcLib}/src/foo/faz.ts`, `export const faz = 'faz';`);

    runCLI(`build ${tscLib} --generateExportsField`);
    runCLI(`build ${swcLib} --generateExportsField`);
    runCLI(`build ${tscEsmLib} --generateExportsField`);
    runCLI(`build ${swcEsmLib} --generateExportsField`);

    expect(readJson(`dist/libs/${tscLib}/package.json`).exports).toEqual({
      './package.json': './package.json',
      '.': './src/index.js',
      './foo/bar': './src/foo/bar.js',
      './foo/faz': './src/foo/faz.js',
    });

    expect(readJson(`dist/libs/${swcLib}/package.json`).exports).toEqual({
      './package.json': './package.json',
      '.': './src/index.js',
      './foo/bar': './src/foo/bar.js',
      './foo/faz': './src/foo/faz.js',
    });

    const pmc = getPackageManagerCommand();
    let output: string;

    // Make sure CJS output is correct
    createFile(
      'test-cjs/package.json',
      JSON.stringify(
        {
          name: 'test-cjs',
          private: true,
          type: 'commonjs',
          dependencies: {
            [`@proj/${tscLib}`]: `file:../dist/libs/${tscLib}`,
            [`@proj/${swcLib}`]: `file:../dist/libs/${swcLib}`,
          },
        },
        null,
        2
      )
    );
    createFile(
      'test-cjs/index.js',
      `
        const { ${tscLib} } = require('@proj/${tscLib}');
        const { ${swcLib} } = require('@proj/${swcLib}');
        // additional entry-points
        const { bar } = require('@proj/${tscLib}/foo/bar');
        const { faz } = require('@proj/${swcLib}/foo/faz');
        console.log(${tscLib}());
        console.log(${swcLib}());
        console.log(bar);
        console.log(faz);
      `
    );
    runCommand(pmc.install, {
      cwd: join(tmpProjPath(), 'test-cjs'),
    });
    output = runCommand('node index.js', {
      cwd: join(tmpProjPath(), 'test-cjs'),
    });
    expect(output).toContain(tscLib);
    expect(output).toContain(swcLib);
    expect(output).toContain('bar');
    expect(output).toContain('faz');

    // Make sure ESM output is correct
    createFile(
      'test-esm/package.json',
      JSON.stringify(
        {
          name: 'test-esm',
          private: true,
          type: 'module',
          dependencies: {
            [`@proj/${tscEsmLib}`]: `file:../dist/libs/${tscEsmLib}`,
            [`@proj/${swcEsmLib}`]: `file:../dist/libs/${swcEsmLib}`,
          },
        },
        null,
        2
      )
    );
    createFile(
      'test-esm/index.js',
      `
        import { ${tscEsmLib} } from '@proj/${tscEsmLib}';
        import { ${swcEsmLib} } from '@proj/${swcEsmLib}';
        console.log(${tscEsmLib}());
        console.log(${swcEsmLib}());
      `
    );
    runCommand(pmc.install, {
      cwd: join(tmpProjPath(), 'test-esm'),
    });
    output = runCommand('node index.js', {
      cwd: join(tmpProjPath(), 'test-esm'),
    });
    expect(output).toContain(tscEsmLib);
    expect(output).toContain(swcEsmLib);
  }, 500_000);
});
