import { recordPluginActivity } from '@shared/api/DataApi';
import { digest } from '@shared/api/digest';
import { enableAutoUpdate } from '@shared/api/wp';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';

export const getActivePlugins = () =>
	apiFetch({ path: 'extendify/v1/auto-launch/active-plugins' });

export const alreadyActive = (activePlugins, pluginSlug) =>
	activePlugins?.filter((p) => p.includes(pluginSlug))?.length;

// WP unpacks every install through one shared dir; two at once corrupt both.
let installQueue = Promise.resolve();

// Re-installing is not free: WP unpacks before it finds the folder exists.
const installed = new Map();

const enqueue = (task) => {
	const result = installQueue.then(task);
	installQueue = result.catch(() => {});
	return result;
};

const INSTALL_DRAIN_MS = 60_000;

// The install POST has no timeout; uncapped, one stall parks the build.
const waitForInstalls = () => {
	let timer;
	return Promise.race([
		installQueue,
		new Promise((resolve) => {
			timer = setTimeout(resolve, INSTALL_DRAIN_MS);
		}),
	]).finally(() => clearTimeout(timer));
};

const install = async (slug) => {
	const fn = async () => {
		const p = await apiFetch({
			path: '/wp/v2/plugins',
			method: 'POST',
			data: { slug },
		});
		// Unawaited: no timeout on these, and a stall would hold the queue.
		recordPluginActivity({ slug, source: 'auto-launch' });
		enableAutoUpdate(p?.plugin);
		return p;
	};
	try {
		return await fn();
	} catch (error) {
		if (error?.code === 'folder_exists') {
			// Already on disk — fetch its record so the caller can activate it.
			return await getPlugin(slug);
		}
		try {
			return await fn();
		} catch (error) {
			digest({
				error,
				details: { source: 'auto-launch', caller: 'installPlugin' },
			});
			return null;
		}
	}
};

export const installPlugin = (slug) =>
	enqueue(async () => {
		const known = installed.get(slug);
		if (known) return { plugin: known };

		const plugin = await install(slug);
		if (plugin?.plugin) installed.set(slug, plugin.plugin);
		return plugin;
	});

export const getPlugin = async (slug) => {
	const response = await apiFetch({
		path: addQueryArgs('/wp/v2/plugins', { search: slug }),
	});
	return response?.find((p) => p.plugin?.split('/')[0] === slug);
};

export const activatePlugin = async (slug) => {
	const fn = (s) =>
		apiFetch({
			path: `/wp/v2/plugins/${s}`,
			method: 'POST',
			data: { status: 'active' },
		});

	try {
		await fn(slug);
		return true;
	} catch (_) {
		try {
			// try once more but get the slug first
			const { plugin } = await getPlugin(slug);
			await fn(plugin);
			return true;
		} catch (error) {
			digest({
				error,
				details: { source: 'auto-launch', caller: 'activatePlugin' },
			});
			return false;
		}
	}
};

// Exact match, not substring: woocommerce-payments would mask a missing woocommerce.
const notActive = (activePlugins, slugs) =>
	slugs.filter(
		(slug) => !activePlugins?.some((path) => path.split('/')[0] === slug),
	);

export const ensurePluginsActive = async (slugs) => {
	// Two callers don't await this; a throw here silently skips every install.
	const active = await getActivePlugins().catch((error) => {
		digest({
			error,
			details: { source: 'auto-launch', caller: 'ensurePluginsActive' },
		});
		return [];
	});
	const missing = notActive(active, slugs);
	if (!missing.length) return { failed: [] };

	const onDisk = window.extSharedData?.installedPluginsSlugs ?? [];
	const failed = [];
	for (const slug of missing) {
		try {
			const plugin = onDisk.includes(slug) ? null : await installPlugin(slug);
			const activated = await activatePlugin(plugin?.plugin ?? slug);
			if (!activated) failed.push(slug);
		} catch (_) {
			failed.push(slug);
		}
	}
	return { failed };
};

export const reportInactivePlugins = async (slugs) => {
	// Falling back to an empty list would name every plugin as inactive.
	const active = await getActivePlugins().catch(() => null);
	if (!active) return;

	const inactive = notActive(active, slugs);
	if (!inactive.length) return;

	digest({
		error: { message: `Plugins inactive after launch: ${inactive.join(', ')}` },
		details: {
			source: 'auto-launch',
			caller: 'reportInactivePlugins',
			inactive,
		},
	});
};

// Currently this only processes patterns with placeholders
// by swapping out the placeholders with the actual code
// returns the patterns as blocks with the placeholders replaced
export const replacePlaceholderPatterns = async (patterns) => {
	// Directly replace "blog-section" patterns using their replacement code, skipping the API call
	patterns = patterns.map((pattern) => {
		if (
			pattern.patternTypes.includes('blog-section') &&
			pattern.patternReplacementCode
		) {
			return {
				...pattern,
				code: pattern.patternReplacementCode,
			};
		}
		return pattern;
	});

	const hasPlaceholders = patterns.filter((p) => p.patternReplacementCode);
	if (!hasPlaceholders?.length) return patterns;

	const activePlugins =
		(await getActivePlugins())?.data?.map((path) => path.split('/')[0]) || [];

	const pluginsActivity = patterns
		.filter((p) => p.pluginDependency)
		.map((p) => p.pluginDependency)
		.filter((p) => !activePlugins.includes(p));

	for (const plugin of pluginsActivity) {
		recordPluginActivity({
			slug: plugin,
			source: 'auto-launch',
		});
	}

	try {
		return await processPlaceholders(patterns);
	} catch (_e) {
		// Try one more time (plugins installed may not be fully loaded)
		return await processPlaceholders(patterns)
			// If this fails, just return the original patterns
			.catch(() => patterns);
	}
};

// This endpoint installs pattern dependencies from PHP, outside the queue.
export const processPlaceholders = async (patterns) => {
	await waitForInstalls();
	return apiFetch({
		path: '/extendify/v1/shared/process-placeholders',
		method: 'POST',
		data: { patterns },
	});
};
