// React Native WebView cannot load external JS files, however it can load
// arbitrary JS via the injectedJavaScript property. So we use this to load external
// files: First here we convert the JS file to a plain string, and that string
// is then loaded by eg. the Mermaid plugin, and finally injected in the WebView.

import { dirname, extname, basename, resolve } from 'path';

import * as esbuild from 'esbuild';
import copyAssets from './copyAssets';
import { writeFile, readFile } from 'fs-extra';

export default class BundledFile {
	private readonly bundleOutputPathBase_: string;
	private readonly bundleBaseName_: string;
	private readonly rootFileDirectory_: string;

	public constructor(
		public readonly bundleName: string,
		private readonly sourceFilePath_: string,
	) {
		this.rootFileDirectory_ = dirname(sourceFilePath_);
		this.bundleBaseName_ = basename(sourceFilePath_, extname(sourceFilePath_));
		this.bundleOutputPathBase_ = `${this.rootFileDirectory_}/${this.bundleBaseName_}.bundle`;
	}

	private makeBuildContext(mode: 'production' | 'development') {
		return esbuild.context({
			entryPoints: [this.sourceFilePath_],
			outfile: `${this.bundleOutputPathBase_}.js`,
			minify: mode === 'production',
			bundle: true,
			sourcemap: true,
			format: 'iife',
			globalName: this.bundleName,
			metafile: false,

			// es5: Have Webpack's generated code target ES5. This doesn't apply to code not
			//      generated by Webpack.
			target: ['chrome58', 'safari14'],

			plugins: [
				{
					name: 'joplin--node-polyfills',
					setup: build => {
						build.onResolve({ filter: /^(path|events)$/ }, args => {
							let path = args.path;
							if (args.path === 'path') {
								path = require.resolve('path-browserify');
							} else if (args.path === 'events') {
								path = require.resolve('events/');
							}

							return { path };
						});
					},
				},
				{
					// Supports require(...)ing SVG images
					name: 'joplin--require-svg',
					setup: build => {
						// A relative path to an SVG:
						build.onResolve({ filter: /^\.{1,2}\/.*\.svg$/ }, args => ({
							path: resolve(args.resolveDir, args.path),
							namespace: 'joplin-require-svg',
						}));

						build.onLoad({ filter: /^.*$/, namespace: 'joplin-require-svg' }, async args => {
							const fileContent = await readFile(args.path, 'utf-8');
							return { contents: `
								let svg = null;
								export default () => {
									svg ??= (() => {
										const parser = new DOMParser();
										const doc = parser.parseFromString(${JSON.stringify(fileContent)}, 'image/svg+xml');
										return doc.querySelector('svg');
									})();
									return svg.cloneNode(true);
								};
							` };
						});
					},
				},
				{
					name: 'joplin--copy-final',
					setup: build => {
						build.onEnd(async (result) => {
							if (result.errors.length === 0) {
								console.log('copy output');
								await this.copyToImportableFile_();
							} else {
								console.warn('Copying skipped. Build produced errors');
							}
						});
					},
				},
			],
		});
	}

	// Creates a file that can be imported by React native. This file contains the
	// bundled JS as a string.
	private async copyToImportableFile_() {
		await copyAssets(`${this.bundleName}.bundle`, {
			js: `${this.bundleOutputPathBase_}.js`,
			css: `${this.bundleOutputPathBase_}.css`,
		});
	}

	// Create a minified JS file in the same directory as `this.sourceFilePath` with
	// the same name.
	public async build() {
		console.info(`Building bundle: ${this.bundleName}...`);
		const compiler = await this.makeBuildContext('production');
		const result = await compiler.rebuild();
		await compiler.dispose();

		if (result?.metafile) {
			await writeFile(`${this.bundleOutputPathBase_}.meta.json`, JSON.stringify(result.metafile, undefined, '\t'));
		}
	}

	public async startWatching() {
		console.info(`Watching bundle: ${this.bundleName}...`);
		const compiler = await this.makeBuildContext('development');
		await compiler.watch();
	}
}
