import { defaultFieldResolver, GraphQLError } from 'graphql';
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils';
import { AuthContextProvider } from '../providers/auth-context.provider.js';
function extractModuleIdFromResolver(resolve) {
    if (typeof resolve !== 'function') {
        return undefined;
    }
    const resolverWithSymbols = resolve;
    for (const symbol of Object.getOwnPropertySymbols(resolve)) {
        const metadata = resolverWithSymbols[symbol];
        if (metadata &&
            typeof metadata === 'object' &&
            'moduleId' in metadata &&
            typeof metadata.moduleId === 'string') {
            return metadata.moduleId;
        }
    }
    return undefined;
}
function resolveInjector(context, moduleId) {
    if (context?.injector) {
        return context.injector;
    }
    if (context?.ɵinjector) {
        return context.ɵinjector;
    }
    if (typeof context?.ɵgetModuleContext !== 'function') {
        return undefined;
    }
    const moduleCandidates = [moduleId, 'auth'].filter((candidate) => !!candidate);
    for (const candidate of moduleCandidates) {
        try {
            const moduleContext = context.ɵgetModuleContext(candidate, context);
            if (moduleContext?.injector) {
                return moduleContext.injector;
            }
        }
        catch {
            // Ignore invalid module candidate and continue with the next fallback.
        }
    }
    return undefined;
}
function throwAuthInfrastructureError(reason) {
    console.error(`[auth-directives] ${reason}`);
    throw new GraphQLError('Authentication subsystem unavailable', {
        extensions: { code: 'INTERNAL_SERVER_ERROR' },
    });
}
export function authDirectiveTransformer(schema) {
    return mapSchema(schema, {
        [MapperKind.OBJECT_FIELD]: fieldConfig => {
            const requiresAuthDirective = getDirective(schema, fieldConfig, 'requiresAuth')?.[0];
            const requiresRoleDirective = getDirective(schema, fieldConfig, 'requiresRole')?.[0];
            const requiresAnyRoleDirective = getDirective(schema, fieldConfig, 'requiresAnyRole')?.[0];
            if (!requiresAuthDirective && !requiresRoleDirective && !requiresAnyRoleDirective) {
                return fieldConfig;
            }
            const { resolve = defaultFieldResolver } = fieldConfig;
            const moduleId = extractModuleIdFromResolver(resolve);
            return {
                ...fieldConfig,
                resolve: async (source, args, context, info) => {
                    const injector = resolveInjector(context, moduleId);
                    if (!injector) {
                        throwAuthInfrastructureError(`Failed to resolve injector for auth directives (moduleId: ${moduleId ?? 'unknown'})`);
                    }
                    const authProvider = injector.get(AuthContextProvider);
                    if (!authProvider) {
                        throwAuthInfrastructureError(`AuthContextProvider is not available in injector (moduleId: ${moduleId ?? 'unknown'})`);
                    }
                    const authContext = await authProvider.getAuthContext();
                    if (!authContext?.user) {
                        throw new GraphQLError('Authentication required', {
                            extensions: { code: 'UNAUTHENTICATED' },
                        });
                    }
                    if (requiresRoleDirective) {
                        const role = requiresRoleDirective.role;
                        if (authContext.user.roleId !== role) {
                            throw new GraphQLError(`Requires role: ${role}`, {
                                extensions: { code: 'FORBIDDEN' },
                            });
                        }
                    }
                    if (requiresAnyRoleDirective) {
                        const roles = requiresAnyRoleDirective.roles;
                        if (!roles.includes(authContext.user.roleId)) {
                            throw new GraphQLError(`Requires one of roles: ${roles.join(', ')}`, {
                                extensions: { code: 'FORBIDDEN' },
                            });
                        }
                    }
                    return resolve(source, args, context, info);
                },
            };
        },
    });
}
//# sourceMappingURL=auth-directives.js.map