import nodeFs from 'node:fs';
import npath from 'node:path';
import type * as vite from 'vite';

type NodeFileSystemModule = typeof nodeFs;

interface LoadFallbackPluginParams {
	fs?: NodeFileSystemModule;
	root: URL;
}

const FALLBACK_FLAG = 'astroFallbackFlag';

export function slash(path: string) {
	return path.replace(/\\/g, '/');
}

const postfixRE = /[?#].*$/s;
export function cleanUrl(url: string): string {
	return url.replace(postfixRE, '');
}

export default function loadFallbackPlugin({ fs, root }: LoadFallbackPluginParams): vite.Plugin[] {
	// Only add this plugin if a custom fs implementation is provided.
	// Also check for `fs.default` because `import * as fs from 'node:fs'` will
	// export as so, which only it's `.default` would === `nodeFs`.
	// @ts-expect-error check default
	if (!fs || fs === nodeFs || fs.default === nodeFs) {
		return [];
	}

	const tryLoadModule = async (id: string) => {
		try {
			// await is necessary for the catch
			return await fs.promises.readFile(cleanUrl(id), 'utf-8');
		} catch {
			try {
				return await fs.promises.readFile(id, 'utf-8');
			} catch {
				try {
					const fullpath = new URL(`.${id}`, root);
					return await fs.promises.readFile(fullpath, 'utf-8');
				} catch {
					// Let fall through to the next
				}
			}
		}
	};

	return [
		{
			name: 'withstudiocms-config-utils:load-fallback',
			enforce: 'post',
			async resolveId(id, parent) {
				// See if this can be loaded from our fs
				if (parent) {
					const candidateId = npath.posix.join(npath.posix.dirname(slash(parent)), id);
					try {
						// Check to see if this file exists and is not a directory.
						const stats = await fs.promises.stat(candidateId);
						if (!stats.isDirectory()) {
							const params = new URLSearchParams(FALLBACK_FLAG);
							return `${candidateId}?${params.toString()}`;
						}
					} catch {}
				}
			},
			load: {
				filter: {
					id: new RegExp(`(?:\\?|&)${FALLBACK_FLAG}(?:&|=|$)`),
				},
				async handler(id) {
					const code = await tryLoadModule(id.slice(0, -(1 + FALLBACK_FLAG.length)));
					if (code) {
						return { code };
					}
				},
			},
		},
		{
			name: 'withstudiocms-config-utils:load-fallback-hmr',
			enforce: 'pre',
			handleHotUpdate(context) {
				// Wrap context.read so it checks our filesystem first.
				const read = context.read;
				context.read = async () => {
					const source = await tryLoadModule(context.file);
					if (source) return source;
					return read.call(context);
				};
			},
		},
	];
}
