import { default as alias } from '@rollup/plugin-alias'
import { default as inject } from '@rollup/plugin-inject'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import { default as typescript } from '@rollup/plugin-typescript'
import { default as MagicString } from 'magic-string'
import {
	readFile as nodeReadFile,
	rename,
	rm,
	writeFile,
} from 'node:fs/promises'
import { createRequire } from 'node:module'
import path from 'node:path'
import { rollup } from 'rollup'

const readFileCache = Object.create(null)
const require = createRequire(import.meta.url)

const readFile = (/** @type {string} */ id) =>
	readFileCache[id] || (readFileCache[id] = nodeReadFile(id, 'utf8'))

const pathToDOMException = path.resolve('src', 'lib', 'DOMException.js')
const pathToEventTargetShim = path.resolve(
	'node_modules',
	'event-target-shim',
	'index.mjs'
)
const pathToStructuredClone = path.resolve(
	'node_modules',
	'@ungap',
	'structured-clone',
	'esm',
	'index.js'
)

const plugins = [
	typescript({
		tsconfig: './tsconfig.json',
	}),
	alias({
		entries: [
			{ find: '@ungap/structured-clone', replacement: pathToStructuredClone },
			{ find: 'event-target-shim', replacement: pathToEventTargetShim },
			{
				find: 'event-target-shim/dist/event-target-shim.js',
				replacement: pathToEventTargetShim,
			},
			{
				find: 'event-target-shim/dist/event-target-shim.umd.js',
				replacement: pathToEventTargetShim,
			},
			{ find: 'node-domexception', replacement: pathToDOMException },
		],
	}),
	nodeResolve({
		dedupe: ['net', 'node:net'],
	}),
	inject({
		// import { Promise as P } from 'es6-promise'
		// P: [ 'es6-promise', 'Promise' ],
		DOMException: [pathToDOMException, 'DOMException'],
		Document: ['./Document', 'Document'],
		Element: ['./Element', 'Element'],
		Event: ['event-target-shim', 'Event'],
		EventTarget: ['event-target-shim', 'EventTarget'],
		defineEventAttribute: ['event-target-shim', 'defineEventAttribute'],
		HTMLElement: ['./Element', 'HTMLElement'],
		HTMLImageElement: ['./Element', 'HTMLImageElement'],
		HTMLUnknownElement: ['./Element', 'HTMLUnknownElement'],
		MediaQueryList: ['./MediaQueryList', 'MediaQueryList'],
		Node: ['./Node', 'Node'],
		ReadableStream: ['node:stream/web', 'ReadableStream'],
		ShadowRoot: ['./Node', 'ShadowRoot'],
		Window: ['./Window', 'Window'],
		'globalThis.ReadableStream': ['node:stream/web', 'ReadableStream'],
	}),
	{
		async load(id) {
			const pathToEsm = id
			const pathToMap = `${pathToEsm}.map`

			const code = await readFile(pathToEsm, 'utf8')

			const indexes = []

			const replacements = [
				// remove unused imports
				[/(^|\n)import\s+[^']+'node:(buffer|fs|path|worker_threads)'/g, ``],
				[/const \{ stat \} = fs/g, ``],

				// remove unused polyfill utils
				[/\nif \(\s*typeof Global[\W\w]+?\n\}/g, ``],
				[/\nif \(\s*typeof window[\W\w]+?\n\}/g, ``],
				[/\nif \(!globalThis\.ReadableStream\) \{[\W\w]+?\n\}/g, ``],
				[/\nif \(typeof SymbolPolyfill[\W\w]+?\n\}/g, ``],

				// remove unused polyfills
				[/\nconst globals = getGlobals\(\);/g, ``],
				[/\nconst queueMicrotask = [\W\w]+?\n\}\)\(\);/g, ``],
				[/\nconst NativeDOMException =[^;]+;/g, ``],
				[
					/\nconst SymbolPolyfill\s*=[^;]+;/g,
					'\nconst SymbolPolyfill = Symbol;',
				],
				[
					/\n(const|let) DOMException[^;]*;/g,
					`let DOMException$1=DOMException`,
				],
				[/\nconst DOMException = globalThis.DOMException[\W\w]+?\}\)\(\)/g, ``],
				[/\nimport DOMException from 'node-domexception'/g, ``],

				// use shared AbortController methods
				[/ new DOMException\$1/g, `new DOMException`],
				[/ from 'net'/g, `from 'node:net'`],
				[/ throw createInvalidStateError/g, `throw new DOMException`],
				[/= createAbortController/g, `= new AbortController`],
				[/\nconst queueMicrotask = [\W\w]+?\n\}\)\(\)\;/g, ``],

				// remove Body.prototype.buffer deprecation notice
				[/\nBody\.prototype\.buffer[^\n]+/g, ``],

				// remove Body.prototype.data deprecation notice
				[/\n	data: \{get: deprecate[\W\w]+?\)\}/g, ``],
			]

			for (const [replacee, replacer] of replacements) {
				replacee.index = 0

				let replaced = null

				while ((replaced = replacee.exec(code)) !== null) {
					const leadIndex = replaced.index
					const tailIndex = replaced.index + replaced[0].length

					indexes.unshift([leadIndex, tailIndex, replacer])
				}
			}

			if (indexes.length) {
				const magicString = new MagicString(code)

				indexes.sort(([leadOfA], [leadOfB]) => leadOfA - leadOfB)

				for (const [leadIndex, tailindex, replacer] of indexes) {
					magicString.overwrite(leadIndex, tailindex, replacer)
				}

				const magicMap = magicString.generateMap({
					source: pathToEsm,
					file: pathToMap,
					includeContent: true,
				})

				const modifiedEsm = magicString.toString()
				const modifiedMap = magicMap.toString()

				return { code: modifiedEsm, map: modifiedMap }
			}
		},
	},
]

async function build() {
	const configs = [
		{
			inputOptions: {
				input: 'src/polyfill.ts',
				plugins: plugins,
				external: ['undici'],
				onwarn(warning, warn) {
					if (warning.code !== 'UNRESOLVED_IMPORT') warn(warning)
				},
			},
			outputOptions: {
				inlineDynamicImports: true,
				file: 'mod.js',
				format: 'esm',
				sourcemap: true,
			},
		},
	]

	for (const config of configs) {
		const bundle = await rollup(config.inputOptions)

		// or write the bundle to disk
		await bundle.write(config.outputOptions)

		// closes the bundle
		await bundle.close()

		// delete the lib directory
		await rm('lib', { force: true, recursive: true })
		await rm('exclusions.d.ts', { force: true, recursive: true })
		await rm('exclusions.d.ts.map', { force: true, recursive: true })
		await rm('inheritance.d.ts', { force: true, recursive: true })
		await rm('inheritance.d.ts.map', { force: true, recursive: true })
		await rm('polyfill.d.ts.map', { force: true, recursive: true })
		await rm('polyfill.js.map', { force: true, recursive: true })
		await rm('polyfill.js', { force: true, recursive: true })
		await rm('ponyfill.d.ts', { force: true, recursive: true })
		await rm('ponyfill.d.ts.map', { force: true, recursive: true })
		await rm('ponyfill.js.map', { force: true, recursive: true })
		await rm('ponyfill.js', { force: true, recursive: true })

		await rename('polyfill.d.ts', 'mod.d.ts')

		const modDTS = await readFile('./mod.d.ts')

		writeFile(
			'mod.d.ts',
			'// organize-imports-ignore\n' +
				modDTS
					.replace('\n//# sourceMappingURL=polyfill.d.ts.map', '')
					.replace('ponyfill.js', 'mod.js')
		)
		writeFile(
			'apply.js',
			`import { polyfill } from './mod.js'\n\nexport * from './mod.js'\n\npolyfill(globalThis)\n`
		)
	}
}

build()