import { CompileOptions, ResolvedOptions } from './options';
import {compile} from 'imba/compiler'
import nfs from 'node:fs'
// @ts-ignore
import { createMakeHot } from 'imba-hmr';
import { ImbaRequest } from './id';
import { safeBase64Hash } from './hash';
import { log } from './log';
import Cache from '../../bundler/cache.imba';

const scriptLangRE = /<script [^>]*lang=["']?([^"' >]+)["']?[^>]*>/;

const _createCompileImba = (imbaConfig, makeHot?: Function) =>
	async function compileImba(
		imbaRequest: ImbaRequest,
		code: string,
		options: Partial<ResolvedOptions>
	): Promise<CompileData> {
		const { filename, normalizedFilename, cssId, ssr } = imbaRequest;
		const { emitCss = true } = options;
		const dependencies = [];
		// todo maybe: generate unique short references for all unique paths, cache them between runs, and send those in via sourceId
		const compileOptions: CompileOptions = {
			...options.compilerOptions,
			filename,
			generate: ssr ? 'ssr' : 'dom',
			format: 'esm',
			resolveColors: true,
			sourcePath: filename,
			vite: true,
			sourcemap: options.compilerOptions.sourcemap ?? "extern",
			...imbaConfig
		};

		if (options.hot && options.emitCss) {
			const hash = `s-${safeBase64Hash(normalizedFilename)}`;
			log.debug(`setting cssHash ${hash} for ${normalizedFilename}`);
			compileOptions.cssHash = () => hash;
		}
		if (ssr && compileOptions.enableSourcemap !== false) {
			if (typeof compileOptions.enableSourcemap === 'object') {
				compileOptions.enableSourcemap.css = false;
			} else {
				compileOptions.enableSourcemap = { js: true, css: false };
			}
		}

		// let preprocessed;

		// if (options.preprocess) {
		// 	try {
		// 		preprocessed = await preprocess(code, options.preprocess, { filename });
		// 	} catch (e) {
		// 		e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
		// 		throw e;
		// 	}

		// 	if (preprocessed.dependencies) dependencies.push(...preprocessed.dependencies);
		// }
		// const finalCode = preprocessed ? preprocessed.code : code;
		// const finalCode = code
		const dynamicCompileOptions = await options.experimental?.dynamicCompileOptions?.({
			filename,
			code,
			compileOptions
		});
		if (dynamicCompileOptions && log.debug.enabled) {
			log.debug(
				`dynamic compile options for  ${filename}: ${JSON.stringify(dynamicCompileOptions)}`
			);
		}
		const finalCompileOptions = dynamicCompileOptions
			? {
					...compileOptions,
					...dynamicCompileOptions
			  }
			: compileOptions;
		finalCompileOptions.config = finalCompileOptions
		finalCompileOptions.styles = "extern"
		finalCompileOptions.platform = "browser"
		finalCompileOptions.vite = true
		const q = imbaRequest.query
		finalCompileOptions.platform = ssr ? "node": "browser"

		if(q.worker){
			finalCompileOptions.platform = "worker"
		}else if(q.web){
			finalCompileOptions.platform = "browser"
		}
		
		if(options.server?.config?.mode == "test" && options.server?.config?.test?.environment == 'node'){
			finalCompileOptions.platform = "node"
		}

		// This is where you potentially cache it?
		// Maybe just add that function to the compiler directly
		const imbacache = globalThis.VITE_IMBA_CACHE;
		const parts = [];
		// match things in code
		const cachekey = `${filename}-${finalCompileOptions.platform}-vite2`;
		const mtime = nfs.existsSync(filename) ? nfs.statSync(filename).mtimeMs : 0;

		const wrap_compile = (code,o) => {
			const res = compile(code, o);
			const map = res.sourcemap;
			return {
				js: {code: res.js, dependencies: [], map},
				css: {code: res.css},
				warnings: res.warnings,
				errors: res.errors
			}
		}
		// Should move into the compiler itself
		// const compiled = await imbacache.memo(cachekey,mtime,()=> Promise.resolve(wrap_compile(code,finalCompileOptions)));
		const compiled = wrap_compile(code,finalCompileOptions);
	
		if (compiled["erroredΦ"]){
			throw compiled
		}

		// const map = compiled.sourcemap
		// compiled.js = {code: compiled.js, map}
		// compiled.css = {code: compiled.css}
		if (emitCss && compiled.css.code) {
			// TODO properly update sourcemap?
			compiled.js.code += `\n/*__css_import__*/import ${JSON.stringify(cssId)};\n`;
		}
		// https://vitejs.dev/guide/api-plugin.html#handlehotupdate
		// only apply hmr when not in ssr context and hot options are set
		if (!ssr && makeHot) {
			compiled.js.code = makeHot({
				id: filename,
				compiledCode: compiled.js.code,
				hotOptions: options.hot,
				compiled,
				originalCode: code,
				compileOptions: finalCompileOptions
			});
		}
		// Dependencies will always be blank?
		compiled.js.dependencies = dependencies;
		return {
			filename,
			normalizedFilename,
			lang: code.match(scriptLangRE)?.[1] || 'js',
			// @ts-ignore
			compiled,
			ssr,
			dependencies
		};
	};

// function buildMakeHot(options: ResolvedOptions) {
// 	const needsMakeHot = options.hot !== false && options.isServe && !options.isProduction;
// 	if (needsMakeHot) {
// 		// @ts-ignore
// 		const hotApi = options?.hot?.hotApi;
// 		// @ts-ignore
// 		const adapter = options?.hot?.adapter;
// 		return createMakeHot({
// 			walk,
// 			hotApi,
// 			adapter,
// 			hotOptions: { noOverlay: true, ...(options.hot as object) }
// 		});
// 	}
// }

export function createCompileImba(options: ResolvedOptions, imbaConfig) {
	// const makeHot = buildMakeHot(options);
	return _createCompileImba(imbaConfig);
}

export interface Code {
	code: string;
	map?: any;
	dependencies?: any[];
}

export interface Compiled {
	js: Code;
	css: Code;
	ast: any; // TODO type
	warnings: any[]; // TODO type
	vars: {
		name: string;
		export_name: string;
		injected: boolean;
		module: boolean;
		mutated: boolean;
		reassigned: boolean;
		referenced: boolean;
		writable: boolean;
		referenced_from_script: boolean;
	}[];
	stats: {
		timings: {
			total: number;
		};
	};
}

export interface CompileData {
	filename: string;
	normalizedFilename: string;
	lang: string;
	compiled: Compiled;
	ssr: boolean | undefined;
	dependencies: string[];
}
