import path from 'path';
import fs from 'fs';
import { rollup, Plugin as RollupPlugin } from 'rollup';
import { swc, PluginOptions, minify } from '../src';
import json from '@rollup/plugin-json';
import commonjs from '@rollup/plugin-commonjs';

import { should } from 'chai';
should();

import { JsMinifyOptions } from '@swc/core';

const tmpDir = path.join(__dirname, '.temp');

const realFs = (folderName: string, files: Record<string, string>) => {
  const testDir = path.join(tmpDir, `rollup-plugin-swc/${folderName}`);
  Object.keys(files).forEach((file) => {
    const absolute = path.join(testDir, file);
    fs.mkdirSync(path.dirname(absolute), { recursive: true });
    fs.writeFileSync(absolute, files[file], 'utf8');
  });
  return testDir;
};

const build = async (
  options?: PluginOptions,
  {
    input = './fixture/index.js',
    otherRollupPlugins = [],
    sourcemap = false,
    dir = '.'
  }: {
    input?: string | string[]
    otherRollupPlugins?: RollupPlugin[]
    sourcemap?: boolean
    dir?: string
  } = {}
) => {
  const build = await rollup({
    input: [...(Array.isArray(input) ? input : [input])].map((v) => path.resolve(dir, v)),
    plugins: [...otherRollupPlugins, swc(options)]
  });
  const { output } = await build.generate({ format: 'esm', sourcemap });
  return output;
};

const runMinify = async (
  options: JsMinifyOptions,
  {
    input = './fixture/index.js',
    otherRollupPlugins = [],
    sourcemap = false,
    dir = '.'
  }
) => {
  const build = await rollup({
    input: [...(Array.isArray(input) ? input : [input])].map((v) => path.resolve(dir, v)),
    plugins: [...otherRollupPlugins, minify(options)]
  });
  const { output } = await build.generate({ format: 'esm', sourcemap });
  return output;
};

const getTestName = () => String(Date.now());

describe('swc', () => {
  before(() => {
    if (fs.existsSync(tmpDir)) {
      fs.rmSync(tmpDir, { recursive: true });
    }
  });

  it('simple', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.js': `
        import Foo from './foo'
        console.log(Foo)
        import bar from './bar'
        console.log(bar)
      `,
      './fixture/foo.tsx': `
        export default class Foo {
          render() {
            return <div className="hehe">hello there!!!</div>
          }
        }
      `,
      './fixture/bar.mjs': `
        const bar = 'baz'
        export default bar
      `
    });
    const output = await build({}, { dir });
    output[0].code.should.equal(`class Foo {
    render() {
        return /*#__PURE__*/ React.createElement("div", {
            className: "hehe"
        }, "hello there!!!");
    }
}

const bar = 'baz';

console.log(Foo);
console.log(bar);
`);
  });

  it('minify', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.js': `
        import Foo from './foo'
        console.log(Foo)
      `,
      './fixture/foo.tsx': `
        export default class Foo {
          render() {
            return <div className="hehe">hello there!!!</div>
          }
        }
      `
    });
    const output = await build({ minify: true, jsc: { target: 'es2022' } }, { dir });
    output[0].code.should.equal(`class Foo{render(){return React.createElement("div",{className:"hehe"},"hello there!!!")}}console.log(Foo)
`);
  });

  it('standalone minify', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.js': `
        console.log(10000);
        console.log('b'      +      'c');
      `
    });
    const output = await runMinify({}, { dir });
    output[0].code.should.equal(`console.log(1e4);console.log("b"+"c")
`);
  });

  it('load index.(x)', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.js': `
        import Foo from './foo'
        console.log(Foo)
      `,
      './fixture/foo/index.tsx': `
        export default class Foo {
          render() {
            return <div className="hehe">hello there!!!</div>
          }
        }
      `
    });

    const output = await build({}, { dir });

    output[0].code.should.equal(`class Foo {
    render() {
        return /*#__PURE__*/ React.createElement("div", {
            className: "hehe"
        }, "hello there!!!");
    }
}

console.log(Foo);
`);
  });

  it('load json', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.js': `
          import foo from './foo.json'
          console.log(foo)
        `,
      './fixture/foo.json': `
        {
          "foo": true
        }
      `
    });

    const output = await build(
      {},
      { otherRollupPlugins: [json()], dir }
    );

    output[0].code.should.equal(`var foo = true;
var foo$1 = {
\tfoo: foo
};

console.log(foo$1);
`);
  });

  it('support rollup virtual module (e.g. commonjs plugin)', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.js': `
        const Foo = require('./foo')
        const { Bar } = require('./bar')
        console.log(Foo, Bar)
      `,
      './fixture/foo.js': `
        module.exports = 'foo'
      `,
      './fixture/bar.js': `
        exports.Bar = 'bar'
      `
    });
    const output = await build(
      {},
      { otherRollupPlugins: [commonjs()], dir }
    );
    output[0].code.should.equal(`var fixture = {};

var foo = 'foo';

var bar = {};

bar.Bar = 'bar';

const Foo = foo;
const { Bar  } = bar;
console.log(Foo, Bar);

export { fixture as default };
`);
  });

  it('use custom jsxFactory (h) from tsconfig', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.tsx': `
        export const foo = <div>foo</div>
      `,
      './fixture/tsconfig.json': `
        {
          "compilerOptions": {
            "jsxFactory": "h"
          }
        }
      `
    });

    const output = await build({}, { input: './fixture/index.tsx', dir });
    output[0].code.should.equal(`var foo = /*#__PURE__*/ h("div", null, "foo");

export { foo };
`);
  });

  it('use custom jsxFactory (h) from jsconfig.json', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.tsx': `
        export const foo = <div>foo</div>
      `,
      './fixture/jsconfig.json': `
        {
          "compilerOptions": {
            "jsxFactory": "h"
          }
        }
      `
    });

    const output = await build({}, { input: './fixture/index.tsx', dir });
    output[0].code.should.equal(`var foo = /*#__PURE__*/ h("div", null, "foo");

export { foo };
`);
  });

  it('use tsconfig.json when tsconfig.json & jsconfig.json both exists', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.tsx': `
        export const foo = <><div>foo</div></>
      `,
      './fixture/jsconfig.json': `
        {
          "compilerOptions": {
            "jsxFactory": "m",
            "jsxFragmentFactory": "React.Fragment"
          }
        }
      `,
      './fixture/tsconfig.json': `
        {
          "compilerOptions": {
          }
        }
    `
    });

    const output = await build({}, { input: './fixture/index.tsx', dir });
    output[0].code.should.equal(`var foo = /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("div", null, "foo"));

export { foo };
`);
  });

  it('use custom tsconfig.json', async () => {
    const dir = realFs(getTestName(), {
      './fixture/index.jsx': `
        export const foo = <div>foo</div>
      `,
      './fixture/tsconfig.json': `
        {
          "compilerOptions": {
            "jsxFactory": "h"
          }
        }
      `,
      './fixture/tsconfig.build.json': `
        {
          "compilerOptions": {
            "jsxFactory": "custom"
          }
        }
      `
    });

    const output = await build(
      { tsconfig: 'tsconfig.build.json' },
      { input: './fixture/index.jsx', dir }
    );
    output[0].code.should.equal(`var foo = /*#__PURE__*/ custom("div", null, "foo");

export { foo };
`);
  });
});