import {
	// transformWithEsbuild,
	// ESBuildOptions,
	ResolvedConfig,
	// TransformResult,
	Plugin
} from 'vite';
// import MagicString from 'magic-string';
// import { preprocess } from 'imba/compiler';
import { Preprocessor, PreprocessorGroup, Processed, ResolvedOptions } from './options';
// import { TransformPluginContext } from 'rollup';
import { log } from './log';
// import { buildSourceMap } from './sourcemap';
// import path from 'path';

// const supportedStyleLangs = ['css', 'less', 'sass', 'scss', 'styl', 'stylus', 'postcss'];

// const supportedScriptLangs = ['ts'];

// function createViteScriptPreprocessor(): Preprocessor {
// 	return async ({ attributes, content, filename = '' }) => {
// 		const lang = attributes.lang as string;
// 		if (!supportedScriptLangs.includes(lang)) return;
// 		const transformResult = await transformWithEsbuild(content, filename, {
// 			loader: lang as ESBuildOptions['loader'],
// 			tsconfigRaw: {
// 				compilerOptions: {
// 					// imba typescript needs this flag to work with type imports
// 					importsNotUsedAsValues: 'preserve',
// 					preserveValueImports: true
// 				}
// 			}
// 		});
// 		return {
// 			code: transformResult.code,
// 			map: transformResult.map
// 		};
// 	};
// }

// function createViteStylePreprocessor(config: ResolvedConfig): Preprocessor {
// 	const pluginName = 'vite:css';
// 	const plugin = config.plugins.find((p) => p.name === pluginName);
// 	if (!plugin) {
// 		throw new Error(`failed to find plugin ${pluginName}`);
// 	}
// 	if (!plugin.transform) {
// 		throw new Error(`plugin ${pluginName} has no transform`);
// 	}
// 	const pluginTransform = plugin.transform!.bind(null as unknown as TransformPluginContext);
// 	return async ({ attributes, content, filename = '' }) => {
// 		const lang = attributes.lang as string;
// 		if (!supportedStyleLangs.includes(lang)) return;
// 		const moduleId = `${filename}.${lang}`;
// 		const transformResult: TransformResult = (await pluginTransform(
// 			content,
// 			moduleId
// 		)) as TransformResult;
// 		// patch sourcemap source to point back to original filename
// 		if (transformResult.map?.sources?.[0] === moduleId) {
// 			transformResult.map.sources[0] = path.basename(filename);
// 		}
// 		return {
// 			code: transformResult.code,
// 			map: transformResult.map ?? undefined
// 		};
// 	};
// }

// function createVitePreprocessorGroup(config: ResolvedConfig): PreprocessorGroup {
// 	return {
// 		markup({ content, filename }) {
// 			return preprocess(
// 				content,
// 				{
// 					script: createViteScriptPreprocessor(),
// 					style: createViteStylePreprocessor(config)
// 				},
// 				{ filename }
// 			);
// 		}
// 	} as PreprocessorGroup;
// }

/**
 * this appends a *{} rule to component styles to force the imba compiler to add style classes to all nodes
 * That means adding/removing class rules from <style> node won't trigger js updates as the scope classes are not changed
 *
 * only used during dev with enabled css hmr
 */
// function createInjectScopeEverythingRulePreprocessorGroup(): PreprocessorGroup {
// 	return {
// 		style({ content, filename }) {
// 			const s = new MagicString(content);
// 			s.append(' *{}');
// 			return {
// 				code: s.toString(),
// 				map: s.generateDecodedMap({
// 					source: filename ? path.basename(filename) : undefined,
// 					hires: true
// 				})
// 			};
// 		}
// 	};
// }

function buildExtraPreprocessors(options: ResolvedOptions, config: ResolvedConfig) {
	const prependPreprocessors: PreprocessorGroup[] = [];
	const appendPreprocessors: PreprocessorGroup[] = [];

	// if (options.experimental?.useVitePreprocess) {
	// 	log.debug('adding vite preprocessor');
	// 	prependPreprocessors.push(createVitePreprocessorGroup(config));
	// }

	// @ts-ignore
	const pluginsWithPreprocessorsDeprecated = config.plugins.filter((p) => p?.imbaPreprocess);
	if (pluginsWithPreprocessorsDeprecated.length > 0) {
		log.warn(
			`The following plugins use the deprecated 'plugin.imbaPreprocess' field. Please contact their maintainers and ask them to move it to 'plugin.api.imbaPreprocess': ${pluginsWithPreprocessorsDeprecated
				.map((p) => p.name)
				.join(', ')}`
		);
		// patch plugin to avoid breaking
		pluginsWithPreprocessorsDeprecated.forEach((p) => {
			if (!p.api) {
				p.api = {};
			}
			if (p.api.imbaPreprocess === undefined) {
				// @ts-ignore
				p.api.imbaPreprocess = p.imbaPreprocess;
			} else {
				log.error(
					`ignoring plugin.imbaPreprocess of ${p.name} because it already defined plugin.api.imbaPreprocess.`
				);
			}
		});
	}

	const pluginsWithPreprocessors: Plugin[] = config.plugins.filter((p) => p?.api?.imbaPreprocess);
	const ignored: Plugin[] = [],
		included: Plugin[] = [];
	for (const p of pluginsWithPreprocessors) {
		if (
			options.ignorePluginPreprocessors === true ||
			(Array.isArray(options.ignorePluginPreprocessors) &&
				options.ignorePluginPreprocessors?.includes(p.name))
		) {
			ignored.push(p);
		} else {
			included.push(p);
		}
	}
	if (ignored.length > 0) {
		log.debug(
			`Ignoring imba preprocessors defined by these vite plugins: ${ignored
				.map((p) => p.name)
				.join(', ')}`
		);
	}
	if (included.length > 0) {
		log.debug(
			`Adding imba preprocessors defined by these vite plugins: ${included
				.map((p) => p.name)
				.join(', ')}`
		);
		appendPreprocessors.push(...pluginsWithPreprocessors.map((p) => p.api.imbaPreprocess));
	}

	// if (options.hot && options.emitCss) {
	// 	appendPreprocessors.push(createInjectScopeEverythingRulePreprocessorGroup());
	// }

	return { prependPreprocessors, appendPreprocessors };
}

export function addExtraPreprocessors(options: ResolvedOptions, config: ResolvedConfig) {
	const { prependPreprocessors, appendPreprocessors } = buildExtraPreprocessors(options, config);
	if (prependPreprocessors.length > 0 || appendPreprocessors.length > 0) {
		if (!options.preprocess) {
			options.preprocess = [...prependPreprocessors, ...appendPreprocessors];
		} else if (Array.isArray(options.preprocess)) {
			options.preprocess.unshift(...prependPreprocessors);
			options.preprocess.push(...appendPreprocessors);
		} else {
			options.preprocess = [...prependPreprocessors, options.preprocess, ...appendPreprocessors];
		}
	}
	const generateMissingSourceMaps = !!options.experimental?.generateMissingPreprocessorSourcemaps;
	if (options.preprocess && generateMissingSourceMaps) {
		options.preprocess = Array.isArray(options.preprocess)
			? options.preprocess.map((p, i) => validateSourceMapOutputWrapper(p, i))
			: validateSourceMapOutputWrapper(options.preprocess, 0);
	}
}

function validateSourceMapOutputWrapper(group: PreprocessorGroup, i: number): PreprocessorGroup {
	const wrapper: PreprocessorGroup = {};

	for (const [processorType, processorFn] of Object.entries(group) as Array<
		// eslint-disable-next-line no-unused-vars
		[keyof PreprocessorGroup, (options: { filename?: string; content: string }) => Processed]
	>) {
		wrapper[processorType] = async (options) => {
			const result = await processorFn(options);

			if (result && result.code !== options.content) {
				let invalidMap = false;
				if (!result.map) {
					invalidMap = true;
					log.warn.enabled &&
						log.warn.once(
							`preprocessor at index ${i} did not return a sourcemap for ${processorType} transform`,
							{
								filename: options.filename,
								type: processorType,
								processor: processorFn.toString()
							}
						);
				} else if ((result.map as any)?.mappings === '') {
					invalidMap = true;
					log.warn.enabled &&
						log.warn.once(
							`preprocessor at index ${i} returned an invalid empty sourcemap for ${processorType} transform`,
							{
								filename: options.filename,
								type: processorType,
								processor: processorFn.toString()
							}
						);
				}
				// if (invalidMap) {
				// 	try {
				// 		const map = await buildSourceMap(options.content, result.code, options.filename);
				// 		if (map) {
				// 			log.debug.enabled &&
				// 				log.debug(
				// 					`adding generated sourcemap to preprocesor result for ${options.filename}`
				// 				);
				// 			result.map = map;
				// 		}
				// 	} catch (e) {
				// 		log.error(`failed to build sourcemap`, e);
				// 	}
				// }
			}
			return result;
		};
	}

	return wrapper;
}
