/**
 * Reads the XSRF-TOKEN cookie set by Spring Security's CookieCsrfTokenRepository.
 * Returns null outside the browser or when the cookie is absent.
 */
export function getCsrfToken(): string | null {
	if (typeof document === 'undefined') return null;
	const match = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/);
	return match ? decodeURIComponent(match[1]) : null;
}

/**
 * Merges the X-XSRF-TOKEN header into a RequestInit so Spring Security's
 * CSRF filter accepts the request. Safe to call server-side (no-op when the
 * cookie is absent).
 */
export function withCsrf(init?: RequestInit): RequestInit {
	const token = getCsrfToken();
	if (!token) return init ?? {};
	const headers = new Headers(init?.headers);
	headers.set('X-XSRF-TOKEN', token);
	return { ...init, headers };
}

/**
 * Wraps a fetch implementation so that every state-mutating call (POST, PUT,
 * PATCH, DELETE) automatically includes the X-XSRF-TOKEN header. GET/HEAD
 * requests pass through unchanged.
 *
 * Used to CSRF-protect client-side hooks that accept an injectable fetchImpl.
 * In unit tests the injected mock is wrapped but getCsrfToken() returns null
 * (no browser cookie), so no header is added and existing test expectations
 * are unaffected.
 */
export function makeCsrfFetch(inner: typeof fetch): typeof fetch {
	return (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
		const method = (init?.method ?? 'GET').toUpperCase();
		if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
			return inner(input, withCsrf(init));
		}
		return inner(input, init);
	};
}

/**
 * Drop-in replacement for fetch that automatically injects X-XSRF-TOKEN on
 * all mutating requests (POST, PUT, PATCH, DELETE). Use this everywhere in
 * client-side code instead of bare fetch + withCsrf().
 *
 * Implemented as a function (not a module-level const) so that test stubs
 * applied via vi.stubGlobal('fetch', mock) are picked up at call time rather
 * than being silently bypassed by a pre-captured reference.
 */
export function csrfFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
	const method = (init?.method ?? 'GET').toUpperCase();
	if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
		return fetch(input, withCsrf(init));
	}
	return fetch(input, init);
}

/**
 * Extracts the fa_session cookie value from a list of Set-Cookie response headers.
 *
 * The backend may append attributes like `Path`, `HttpOnly`, `SameSite=Strict`,
 * `Max-Age`, `Secure`; we only forward the opaque session id — the SvelteKit
 * cookies API rewrites the attributes itself when re-emitting to the browser.
 *
 * Pass the result of `response.headers.getSetCookie()` (modern Node/Undici) or
 * a single-element array containing `response.headers.get('set-cookie')` for
 * older runtimes that lack `getSetCookie`.
 *
 * Returns `null` if no fa_session cookie is present.
 */
export function extractFaSessionId(setCookieHeaders: string[]): string | null {
	for (const header of setCookieHeaders) {
		const match = header.match(/^fa_session=([^;]+)/);
		if (match) return match[1];
	}
	return null;
}
