import { kratosServerClient } from './kratos-server';
import type {
	LoginFlow,
	RegistrationFlow,
	RecoveryFlow,
	VerificationFlow,
	SettingsFlow
} from '@ory/client';

// Utility to (a) get flow if id present, (b) create the new browser flow if not, and
// (c) transparently re-init on stale/expired/forbidden flows similar to Ory Kratos UI
async function getOrCreateFlow<
	T extends LoginFlow | RegistrationFlow | RecoveryFlow | VerificationFlow | SettingsFlow
>(params: {
	flowId: string | null;
	create: () => Promise<T>;
	get: () => Promise<T>;
	redirectBasePath: string; // e.g. '/login'
	searchParams?: URLSearchParams;
	excludeParams?: string[]; // Additional params to exclude from redirect (e.g., 'code' for verification/recovery)
}): Promise<{ flow: T; redirectTo?: string }> {
	const { flowId, create, get, redirectBasePath, searchParams, excludeParams = [] } = params;
	const buildRedirect = (flow: T) => {
		const sp = new URLSearchParams();
		// Parameters to exclude from redirect URL
		const excluded = new Set(['flow', ...excludeParams]);
		// Only copy non-excluded search params
		if (searchParams) {
			for (const [key, value] of searchParams.entries()) {
				if (!excluded.has(key)) {
					sp.set(key, value);
				}
			}
		}
		sp.set('flow', flow.id);
		return `${redirectBasePath}?${sp.toString()}`;
	};

	try {
		if (flowId) {
			const flow = await get();
			return { flow };
		}

		const flow = await create();
		return { flow, redirectTo: buildRedirect(flow) };
	} catch (e: any) {
		// Handle common Kratos flow errors by re-initializing the flow
		// 410 (Gone) - flow expired; 403 (Forbidden) - CSRF or not allowed; 400 (Bad Request) - invalid id
		if ([410, 403, 400].includes(e?.status || e?.response?.status)) {
			try {
				const flow = await create();
				return { flow, redirectTo: buildRedirect(flow) };
			} catch (ee: any) {
				throw ee;
			}
		}
		throw e;
	}
}

export async function loadRecoveryFlow(flowId: string | null, searchParams?: URLSearchParams) {
	return getOrCreateFlow<RecoveryFlow>({
		flowId,
		create: async () => (await kratosServerClient.createBrowserRecoveryFlow()).data,
		get: async () => (await kratosServerClient.getRecoveryFlow({ id: flowId! })).data,
		redirectBasePath: '/recovery',
		searchParams,
		excludeParams: ['code'] // Don't preserve recovery codes across flow recreations
	});
}

export async function loadVerificationFlow(flowId: string | null, searchParams?: URLSearchParams) {
	return getOrCreateFlow<VerificationFlow>({
		flowId,
		create: async () => (await kratosServerClient.createBrowserVerificationFlow()).data,
		get: async () => (await kratosServerClient.getVerificationFlow({ id: flowId! })).data,
		redirectBasePath: '/verification',
		searchParams,
		excludeParams: ['code'] // Don't preserve verification codes across flow recreations
	});
}

export async function loadSettingsFlow(
	flowId: string | null,
	cookie: string,
	searchParams?: URLSearchParams
) {
	return getOrCreateFlow<SettingsFlow>({
		flowId,
		create: async () => (await kratosServerClient.createBrowserSettingsFlow({ cookie })).data,
		get: async () => (await kratosServerClient.getSettingsFlow({ id: flowId!, cookie })).data,
		redirectBasePath: '/settings',
		searchParams
	});
}
