import * as i0 from '@angular/core';
import { InjectionToken, Injector, Injectable, signal, inject, Input, Directive, provideAppInitializer, Optional, Inject, NgModule, input, output, Component } from '@angular/core';
import { AXPBroadcastEventService, AXP_SESSION_SERVICE } from '@acorex/platform/core';
import { isEmpty } from 'lodash-es';
import { map, BehaviorSubject, shareReplay, defaultIfEmpty, switchMap, filter, from, first } from 'rxjs';

const AXP_APPLICATION_LOADER = new InjectionToken('AXP_APPLICATION_LOADER', {
    providedIn: 'root',
    factory: () => {
        return new AXPApplicationDefaultLoader();
    },
});
class AXPApplicationDefaultLoader {
    async getList(context) {
        return [
            {
                id: '1',
                name: 'default-app',
                title: 'Default Application',
                version: '1.0.0',
                edition: {
                    id: 'default-edition-1',
                    title: 'Standard',
                },
            },
            {
                id: '2',
                name: 'default-app',
                title: 'Default Application',
                version: '1.0.0',
                edition: {
                    id: 'default-edition-2',
                    title: 'Standard',
                },
            },
        ];
    }
}

const AXP_TENANT_LOADER = new InjectionToken('AXP_TENANT_LOADER', {
    providedIn: 'root',
    factory: () => {
        return new AXPTenantDefaultLoader();
    },
});
class AXPTenantDefaultLoader {
    async getList(context) {
        return [
            {
                id: '1',
                name: 'default-tenant',
                title: 'Default Tenant',
            },
        ];
    }
}

class AXPAuthStrategyRegistryService {
    constructor(injector) {
        this.strategies = new Map();
        this.injector = injector;
    }
    register(...plugins) {
        plugins.forEach(t => {
            const childInjector = Injector.create({ providers: [{ provide: t, useClass: t, deps: [] }], parent: this.injector });
            const strategy = childInjector.get(t);
            if (strategy) {
                this.strategies.set(strategy.name, strategy);
            }
        });
    }
    get(strategyKey) {
        return this.strategies.get(strategyKey);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPAuthStrategyRegistryService, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPAuthStrategyRegistryService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPAuthStrategyRegistryService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }], ctorParameters: () => [{ type: i0.Injector }] });

const AXP_FEATURE_LOADER = new InjectionToken('AXP_FEATURE_LOADER', {
    providedIn: 'root',
    factory: () => {
        return new AXPFeatureDefaultLoader();
    },
});
class AXPFeatureDefaultLoader {
    async getList(context) {
        return [];
    }
}

class AXPFeatureDirective {
    constructor(templateRef, viewContainer) {
        this.templateRef = templateRef;
        this.viewContainer = viewContainer;
        this.hasView = signal(false, ...(ngDevMode ? [{ debugName: "hasView" }] : []));
        this.sessionService = inject(AXPSessionService);
    }
    set feature(featureKeys) {
        const keys = !featureKeys ? [] : (Array.isArray(featureKeys) ? featureKeys : [featureKeys]);
        if (keys.length == 0) {
            // If featureKey is null or empty, decide the default behavior here
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.hasView.set(true);
            return;
        }
        this.subscription = this.sessionService.features$
            //.pipe(first())
            .subscribe(() => {
            if (this.sessionService.isFeatureEnabled(...keys)) {
                if (!this.hasView()) {
                    this.viewContainer.createEmbeddedView(this.templateRef);
                    this.hasView.set(true);
                }
            }
            else {
                this.viewContainer.clear();
                this.hasView.set(false);
            }
        });
    }
    set featureElse(elseTemplateRef) {
        if (!this.hasView()) {
            this.viewContainer.createEmbeddedView(elseTemplateRef);
        }
    }
    ngOnDestroy() {
        this.subscription?.unsubscribe();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPFeatureDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: AXPFeatureDirective, isStandalone: false, selector: "[feature]", inputs: { feature: "feature", featureElse: "featureElse" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPFeatureDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[feature]',
                    standalone: false
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }], propDecorators: { feature: [{
                type: Input
            }], featureElse: [{
                type: Input
            }] } });

class AXPUnauthorizedError extends Error {
    constructor(message, data) {
        super(message);
        this.data = data;
        this.name = 'AXPUnauthorizedError';
    }
}
class AXPUnauthenticatedError extends Error {
    constructor(message, data) {
        super(message);
        this.data = data;
        this.name = 'AXPUnauthenticatedError';
    }
}

const AXPFeatureGuard = (route, state) => {
    const sessionService = inject(AXPSessionService);
    const requiredFeatures = route.data['requiredFeature'];
    return sessionService.features$.pipe(map(() => {
        const keys = !requiredFeatures ? [] : (Array.isArray(requiredFeatures) ? requiredFeatures : [requiredFeatures]);
        const hasFeature = keys.length == 0 || sessionService.isFeatureEnabled(...keys);
        if (!hasFeature) {
            throw new AXPUnauthorizedError(`Access Denied. You do not have access to this feature. Required feature(s): ${keys.join(', ')}. Please contact support if you need access.`, {
                redirectUrl: state.url
            });
        }
        return true;
    }));
};

/**
 * Optional injection token for feature checker.
 * If provided, the checker will be called to potentially override
 * feature enablement results based on custom logic.
 */
const AXP_FEATURE_CHECKER = new InjectionToken('AXP_FEATURE_CHECKER');

const AXP_PERMISSION_LOADER = new InjectionToken('AXP_PERMISSION_LOADER', {
    providedIn: 'root',
    factory: () => {
        return new AXPPermissionDefaultLoader();
    }
});
class AXPPermissionDefaultLoader {
    async getList(context) {
        return [];
    }
}

/**
 * Optional injection token for permission checker.
 * If provided, the checker will be called to potentially override
 * authorization results based on custom logic.
 */
const AXP_PERMISSION_CHECKER = new InjectionToken('AXP_PERMISSION_CHECKER');

class AXPSessionContext {
    get user() {
        return this._user;
    }
    get tenant() {
        return this._tenant;
    }
    get application() {
        return this._application;
    }
    constructor(context) {
        this._user = null;
        this._tenant = null;
        this._application = null;
        this._user = context.user;
        this._tenant = context.tenant;
        this._application = context.application;
    }
}
var AXPSessionStatus;
(function (AXPSessionStatus) {
    AXPSessionStatus["Authenticated"] = "authenticated";
    AXPSessionStatus["Unauthenticated"] = "unauthenticated";
    AXPSessionStatus["Unauthorized"] = "unauthorized";
    AXPSessionStatus["Authorized"] = "authorized";
    AXPSessionStatus["Expired"] = "expired";
    AXPSessionStatus["SignedOut"] = "signedOut";
})(AXPSessionStatus || (AXPSessionStatus = {}));

class AXPSessionService {
    constructor() {
        this.eventService = inject(AXPBroadcastEventService);
        this.authStrategyRegistry = inject(AXPAuthStrategyRegistryService);
        this.injector = inject(Injector);
        this.permissionLoader = inject(AXP_PERMISSION_LOADER);
        this.featureLoader = inject(AXP_FEATURE_LOADER);
        this.tenantLoader = inject(AXP_TENANT_LOADER);
        this.applicationLoader = inject(AXP_APPLICATION_LOADER);
        this.permissionChecker = inject(AXP_PERMISSION_CHECKER, { optional: true });
        this.featureChecker = inject(AXP_FEATURE_CHECKER, { optional: true });
        this.status = new BehaviorSubject(AXPSessionStatus.Unauthenticated);
        this.status$ = this.status.asObservable().pipe(shareReplay(1));
        // Add loading state to prevent premature redirects
        this.isLoading = new BehaviorSubject(true);
        this.isLoading$ = this.isLoading.asObservable().pipe(shareReplay(1));
        this.currentUserSubject = new BehaviorSubject(null);
        this.user$ = this.currentUserSubject.asObservable().pipe(shareReplay(1));
        this.currentTenantSubject = new BehaviorSubject(null);
        this.tenant$ = this.currentTenantSubject.asObservable().pipe(shareReplay(1));
        this.currentApplicationSubject = new BehaviorSubject(null);
        this.application$ = this.currentApplicationSubject.asObservable().pipe(shareReplay(1));
        this.permissionsSubject = new BehaviorSubject([]);
        this.permissions$ = this.permissionsSubject.asObservable().pipe(shareReplay(1), defaultIfEmpty([]));
        this.featuresSubject = new BehaviorSubject([]);
        this.features$ = this.featuresSubject.asObservable().pipe(shareReplay(1), defaultIfEmpty([]));
        this.isAuthenticated$ = this.status$.pipe(map((status) => {
            const isAuth = status === AXPSessionStatus.Authenticated || status === AXPSessionStatus.Authorized;
            return isAuth;
        }), shareReplay(1));
        // Add a new observable that considers loading state
        this.isAuthenticatedWithLoading$ = this.isLoading$.pipe(switchMap((loading) => {
            if (loading) {
                // Wait for loading to complete, then return authentication status
                return this.isLoading$.pipe(filter((isLoading) => !isLoading), switchMap(() => this.isAuthenticated$));
            }
            // If not loading, return current authentication status
            return this.isAuthenticated$;
        }), shareReplay(1));
        this.isAuthorized$ = this.status$.pipe(map((status) => status === AXPSessionStatus.Authorized), shareReplay(1));
    }
    static { this.SESSION_KEY = 'AXP_SESSION'; }
    get user() {
        const session = this.getSessionData();
        if (session?.user && !this.currentUserSubject.value) {
            this.currentUserSubject.next(session.user);
        }
        return this.currentUserSubject.value;
    }
    get tenant() {
        const session = this.getSessionData();
        if (session?.tenant && !this.currentTenantSubject.value) {
            this.currentTenantSubject.next(session.tenant);
        }
        return this.currentTenantSubject.value;
    }
    get tenants$() {
        return from(this.tenantLoader.getList(this.getContext()));
    }
    get application() {
        const session = this.getSessionData();
        if (session?.application && !this.currentApplicationSubject.value) {
            this.currentApplicationSubject.next(session.application);
        }
        return this.currentApplicationSubject.value;
    }
    get applications$() {
        return from(this.applicationLoader.getList(this.getContext()));
    }
    get permissions() {
        return this.permissionsSubject.value ?? [];
    }
    get features() {
        return this.featuresSubject.value ?? [];
    }
    async restoreSession() {
        this.isLoading.next(true);
        try {
            const sessionData = this.getSessionData();
            if (sessionData) {
                if (sessionData.user) {
                    this.currentUserSubject.next(sessionData.user);
                    // Restore tenant and application from session data
                    if (sessionData.tenant) {
                        this.currentTenantSubject.next(sessionData.tenant);
                    }
                    if (sessionData.application) {
                        this.currentApplicationSubject.next(sessionData.application);
                    }
                    this.status.next(AXPSessionStatus.Authenticated);
                    await this.loadPermissions();
                    await this.loadFeatures();
                    await this.signInComplete();
                }
            }
            else {
                this.status.next(AXPSessionStatus.Unauthorized);
            }
        }
        catch (error) {
            this.status.next(AXPSessionStatus.Unauthorized);
        }
        finally {
            this.isLoading.next(false);
        }
    }
    async signin(credentials) {
        this.isLoading.next(true);
        try {
            //
            // this.clearSession();
            //
            const strategy = this.authStrategyRegistry.get(credentials.strategy);
            if (!strategy) {
                throw new Error(`Authentication strategy '${credentials.strategy}' is not supported`);
            }
            const result = await strategy.signin(credentials);
            if (!result)
                return;
            if (result?.succeed) {
                this.currentUserSubject.next(result.data.user);
                this.setSession({
                    accessToken: result.data.accessToken,
                    refreshToken: result.data.refreshToken,
                    strategy: credentials.strategy,
                    user: result.data.user,
                    application: result.data?.application,
                    tenant: result.data?.tenant,
                    expiresIn: result.data?.expiresIn,
                    idToken: result.data?.idToken ?? null,
                });
            }
            else {
                this.status.next(AXPSessionStatus.Unauthenticated);
                throw new Error(`Invalid Username or Password`);
            }
        }
        catch (error) {
            console.error('Signin error:', error);
            this.status.next(AXPSessionStatus.Unauthenticated);
            throw error;
        }
        finally {
            this.isLoading.next(false);
            console.log('Signin process completed');
        }
    }
    async updateToken(params) {
        this.isLoading.next(true);
        try {
            const strategyName = this.getSessionData()?.strategy;
            if (!strategyName) {
                throw new Error('Strategy not found');
            }
            const strategy = this.authStrategyRegistry.get(strategyName);
            if (!strategy) {
                throw new Error(`Authentication strategy '${this.getSessionData()?.strategy}' is not supported`);
            }
            const result = await strategy.updateToken(params);
            if (result?.succeed) {
                this.currentUserSubject.next(result.data.user);
                this.setSession({
                    accessToken: result.data.accessToken,
                    refreshToken: result.data.refreshToken,
                    strategy: strategyName,
                    user: result.data.user,
                    application: result.data?.application,
                    tenant: result.data?.tenant,
                    expiresIn: result.data?.expiresIn,
                    idToken: result.data?.idToken ?? null,
                });
                this.status.next(AXPSessionStatus.Authenticated);
                // If we have both tenant and application, complete the sign-in
                if (this.application && this.tenant) {
                    await this.restoreSession();
                }
            }
            else {
                this.status.next(AXPSessionStatus.Unauthenticated);
                throw new Error(`Invalid Username or Password`);
            }
        }
        catch (error) {
            console.error('Update token error:', error);
            throw error;
        }
        finally {
            this.isLoading.next(false);
            console.log('Update token process completed');
        }
    }
    async signout() {
        console.log('Signing out...');
        this.isLoading.next(true);
        try {
            const sessionData = this.getSessionData();
            if (sessionData?.strategy) {
                const strategy = this.authStrategyRegistry.get(sessionData?.strategy);
                if (strategy) {
                    try {
                        await strategy.signout();
                    }
                    catch (error) { }
                }
            }
            //
            const userId = this.user?.id;
            this.clearSession();
            this.eventService.publish(AXPSessionStatus.SignedOut, { id: userId });
            this.isLoading.next(false);
            this.status.next(AXPSessionStatus.SignedOut);
        }
        finally {
            this.isLoading.next(false);
        }
    }
    async refreshToken() {
        console.log('Refreshing token...');
        return new Promise(async (resolve, reject) => {
            const sessionData = this.getSessionData();
            if (!sessionData || !sessionData?.refreshToken) {
                console.log('No session data or refresh token found');
                reject(new Error('No refresh token available'));
                return;
            }
            const strategy = this.authStrategyRegistry.get(sessionData.strategy);
            if (!strategy) {
                console.error('Authentication strategy not found:', sessionData.strategy);
                reject(new Error(`Authentication strategy '${sessionData.strategy}' is not found`));
                return;
            }
            try {
                const result = await strategy.refreshToken(this.getContext());
                if (result.succeed) {
                    console.log('Token refresh successful');
                    this.setSession(result.data);
                    resolve(result.data?.accessToken);
                }
                else {
                    console.error('Token refresh failed');
                    this.clearSession();
                    this.status.next(AXPSessionStatus.Expired);
                    reject(new Error('Token refresh failed'));
                }
            }
            catch (error) {
                console.error('Error during token refresh:', error);
                this.clearSession();
                this.status.next(AXPSessionStatus.Expired);
                reject(error);
            }
        });
    }
    async loadPermissions() {
        try {
            const permissions = await this.permissionLoader.getList(this.getContext());
            this.permissionsSubject.next(permissions ?? []);
        }
        catch (error) {
            console.error('Error loading permissions:', error);
            this.permissionsSubject.next([]);
        }
    }
    async loadFeatures() {
        try {
            const features = await this.featureLoader.getList(this.getContext());
            this.featuresSubject.next(features ?? []);
        }
        catch (error) {
            console.error('Error loading features:', error);
            this.featuresSubject.next([]);
        }
    }
    async signInComplete() {
        // Ensure we have the required data
        if (!this.user) {
            this.status.next(AXPSessionStatus.Unauthenticated);
            return;
        }
        // Set status to Authorized
        this.status.next(AXPSessionStatus.Authorized);
        // Double-check the status was set correctly
        setTimeout(() => {
            console.log('Status after timeout:', this.status.value);
        }, 100);
    }
    setSession(tokens) {
        const sessionData = {
            accessToken: tokens.accessToken,
            refreshToken: tokens.refreshToken,
            strategy: tokens.strategy,
            user: tokens.user,
            application: tokens.application,
            tenant: tokens.tenant,
            expiresIn: tokens.expiresIn,
            idToken: tokens.idToken,
        };
        // Update subjects
        if (tokens.user) {
            this.currentUserSubject.next(tokens.user);
        }
        if (tokens.tenant) {
            this.currentTenantSubject.next(tokens.tenant);
        }
        if (tokens.application) {
            this.currentApplicationSubject.next(tokens.application);
        }
        localStorage.setItem(AXPSessionService.SESSION_KEY, JSON.stringify(sessionData));
    }
    setStrategy(strategy) {
        const sessionData = this.getSessionData();
        const newSessionData = { ...sessionData, strategy };
        this.setSession(newSessionData);
    }
    getSessionData() {
        try {
            const sessionDataString = localStorage.getItem(AXPSessionService.SESSION_KEY);
            if (sessionDataString) {
                const sessionData = JSON.parse(sessionDataString);
                return sessionData;
            }
            else {
                return null;
            }
        }
        catch (error) {
            localStorage.removeItem(AXPSessionService.SESSION_KEY);
            return null;
        }
    }
    clearSession() {
        //
        this.currentUserSubject.next(null);
        //
        this.currentTenantSubject.next(null);
        //
        this.currentApplicationSubject.next(null);
        //
        this.permissionsSubject.next([]);
        //
        this.featuresSubject.next([]);
        //
        localStorage.removeItem(AXPSessionService.SESSION_KEY);
    }
    authorize(...keys) {
        // Calculate base result
        const baseResult = keys.every((k) => {
            if (isEmpty(k))
                return true;
            // Check if user has the permission
            const hasPermission = this.permissions.indexOf(k) > -1;
            if (!hasPermission)
                return false;
            // Check if permission has required features (if permission definition service is available)
            // Note: This is a lightweight check. Full feature validation happens in permission definition service.
            return true;
        });
        // If permission checker is provided, use it to potentially override the result
        if (this.permissionChecker) {
            const context = this.getContext();
            return this.permissionChecker.check(keys, context, baseResult);
        }
        return baseResult;
    }
    isFeatureEnabled(...keys) {
        // Calculate base result
        const baseResult = keys.every((k) => isEmpty(k) || this.features.some((c) => c.name == k && c.value == true));
        // If feature checker is provided, use it to potentially override the result
        if (this.featureChecker) {
            const context = this.getContext();
            return this.featureChecker.check(keys, context, baseResult);
        }
        return baseResult;
    }
    /**
     * Checks if a module is enabled for the current tenant/application.
     * Module names are stored as features with value: true when enabled.
     * Module names can be provided in PascalCase (e.g., 'SecurityManagement') or kebab-case (e.g., 'security-management').
     */
    isModuleEnabled(moduleName) {
        if (!moduleName) {
            return false;
        }
        // Normalize module name: convert kebab-case to PascalCase if needed
        const normalizedModuleName = moduleName.includes('-')
            ? moduleName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('')
            : moduleName;
        // Module names are stored as features with value: true when enabled
        return this.features.some(f => f.name === normalizedModuleName && f.value === true);
    }
    getToken() {
        const sessionData = this.getSessionData();
        return sessionData?.accessToken;
    }
    getContext() {
        return new AXPSessionContext({
            user: this.user,
            tenant: this.tenant,
            application: this.application,
        });
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPSessionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPSessionService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPSessionService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

class AXPPermissionDirective {
    constructor(templateRef, viewContainer) {
        this.templateRef = templateRef;
        this.viewContainer = viewContainer;
        this.hasView = signal(false, ...(ngDevMode ? [{ debugName: "hasView" }] : []));
        this.sessionService = inject(AXPSessionService);
    }
    set permission(permissionKeys) {
        const keys = !permissionKeys ? [] : (Array.isArray(permissionKeys) ? permissionKeys : [permissionKeys]);
        if (keys.length == 0) {
            // If permissionKey is null or empty, decide the default behavior here
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.hasView.set(true);
            return;
        }
        this.subscription = this.sessionService.isAuthorized$
            .subscribe((isAuthorized) => {
            if (isAuthorized && this.sessionService.authorize(...keys)) {
                if (!this.hasView()) {
                    this.viewContainer.createEmbeddedView(this.templateRef);
                    this.hasView.set(true);
                }
            }
            else {
                this.viewContainer.clear();
                this.hasView.set(false);
            }
        });
    }
    set permissionElse(elseTemplateRef) {
        if (!this.hasView()) {
            this.viewContainer.createEmbeddedView(elseTemplateRef);
        }
    }
    ngOnDestroy() {
        this.subscription?.unsubscribe();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPPermissionDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: AXPPermissionDirective, isStandalone: false, selector: "[permission]", inputs: { permission: "permission", permissionElse: "permissionElse" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPPermissionDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[permission]',
                    standalone: false
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }], propDecorators: { permission: [{
                type: Input
            }], permissionElse: [{
                type: Input
            }] } });

const AXPPermissionGuard = (route, state) => {
    const sessionService = inject(AXPSessionService);
    const permissionKeys = route.data['requiredPermission'];
    return sessionService.isAuthorized$.pipe(first(), map((value) => {
        const keys = !permissionKeys ? [] : Array.isArray(permissionKeys) ? permissionKeys : [permissionKeys];
        const hasPermission = keys.length == 0 || sessionService.authorize(...keys);
        if (!hasPermission) {
            throw new AXPUnauthorizedError(`Access denied. Required permissions: ${keys.join(', ')}. Please contact your administrator if you believe this is an error.`, {
                redirectUrl: state.url
            });
        }
        return true;
    }));
};

class AXPPermissionDefinitionProviderContext {
    constructor() {
        this.builders = new Map();
    }
    addGroup(name, title, description) {
        if (this.isGroupDefined(name)) {
            return this.findGroup(name);
        }
        const group = {
            name,
            title,
            description,
            permissions: []
        };
        const builder = new AXPPermissionDefinitionGroupBuilder(this, group);
        this.builders.set(name, builder);
        return builder;
    }
    getGroupDefinitions() {
        return Array.from(this.builders.values()).map(b => b.group);
    }
    findGroup(name) {
        return this.builders.get(name);
    }
    isGroupDefined(name) {
        return this.builders.has(name);
    }
}
class AXPPermissionDefinitionGroupBuilder {
    get group() {
        return this._group;
    }
    constructor(context, group) {
        this.context = context;
        this._group = group;
    }
    addPermission(name, title, description, requiredFeatures) {
        const permission = {
            name,
            title,
            description,
            children: [],
            requiredFeatures
        };
        this._group.permissions.push(permission);
        return new AXPPermissionDefinitionBuilder(this, permission);
    }
    endGroup() {
        return this.context;
    }
    findPermission(path) {
        return undefined;
    }
    findGroup(name) {
        return undefined;
    }
}
class AXPPermissionDefinitionBuilder {
    constructor(groupBuilder, permission) {
        this.groupBuilder = groupBuilder;
        this.permission = permission;
    }
    addChild(name, title, description, requiredFeatures) {
        const permission = {
            name,
            title,
            description,
            children: [],
            requiredFeatures
        };
        this.permission.children.push(permission);
        return this;
    }
    /**
     * Set required features for this permission.
     * @param features - Array of feature names (e.g., ['PlatformManagement.menu-customization'])
     */
    requireFeatures(...features) {
        this.permission.requiredFeatures = features;
        return this;
    }
    endPermission() {
        return this.groupBuilder;
    }
}

const AXP_PERMISSION_DEFINITION_PROVIDER = new InjectionToken('AXP_PERMISSION_DEFINITION_PROVIDER', {
    providedIn: 'root',
    factory: () => {
        return [];
    }
});
class AXPPermissionDefinitionService {
    constructor() {
        this.providers = inject(AXP_PERMISSION_DEFINITION_PROVIDER, { optional: true });
        this.sessionService = inject(AXPSessionService);
        this.cache = null;
    }
    async load() {
        if (this.cache) {
            return;
        }
        const context = new AXPPermissionDefinitionProviderContext();
        // Load providers from DI tokens
        if (Array.isArray(this.providers)) {
            for (const provider of this.providers) {
                if (provider instanceof Promise) {
                    // If provider is a promise, resolve it
                    const resolvedProvider = await provider;
                    await resolvedProvider.define(context);
                }
                else {
                    // If provider is a direct instance, use it directly
                    await provider.define(context);
                }
            }
        }
        // Filter permissions based on required features
        const allGroups = context.getGroupDefinitions();
        this.cache = this.filterByFeatures(allGroups);
    }
    /**
     * Filter permissions based on required features.
     * Removes permissions that have required features that are not enabled.
     */
    filterByFeatures(groups) {
        return groups.map(group => ({
            ...group,
            permissions: this.filterPermissions(group.permissions)
        })).filter(group => group.permissions.length > 0);
    }
    /**
     * Recursively filter permissions and their children based on required features.
     */
    filterPermissions(permissions) {
        return permissions
            .filter(permission => {
            // Check if required features are enabled
            if (permission.requiredFeatures && permission.requiredFeatures.length > 0) {
                const allFeaturesEnabled = permission.requiredFeatures.every(featureName => this.sessionService.isFeatureEnabled(featureName));
                if (!allFeaturesEnabled) {
                    return false; // Filter out this permission
                }
            }
            return true;
        })
            .map(permission => ({
            ...permission,
            children: this.filterPermissions(permission.children)
        }))
            .filter(permission => {
            // Remove permissions that have no children if they were only containers
            // (This is optional - you might want to keep parent permissions even without children)
            return true;
        });
    }
    async reload() {
        this.cache = null;
        await this.load();
    }
    async getGroups() {
        await this.load();
        return this.cache ?? [];
    }
    async getPermissions() {
        await this.load();
        return this.cache?.flatMap(g => g.permissions) ?? [];
    }
    async getGroup(name) {
        await this.load();
        return this.cache?.find(g => g.name === name) ?? null;
    }
    async getPermission(name) {
        await this.load();
        return this.cache?.find(g => g.permissions.find(p => p.name === name))?.permissions.find(p => p.name === name) ?? null;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPPermissionDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPPermissionDefinitionService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPPermissionDefinitionService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }] });

class AXPPermissionEvaluatorScopeProvider {
    constructor() {
        this.sessionService = inject(AXPSessionService);
    }
    async provide(context) {
        context.addScope('permission', {
            /**
             * Check if the current user has all specified permissions
             * @param permissions - Single permission string or array of permission strings
             * @returns boolean - true if user has all permissions, false otherwise
             */
            check: (...permissions) => {
                return this.sessionService.authorize(...permissions);
            },
            /**
             * Check if the current user has any of the specified permissions
             * @param permissions - Array of permission strings
             * @returns boolean - true if user has any of the permissions, false otherwise
             */
            checkAny: (...permissions) => {
                return permissions.some(p => this.sessionService.authorize(p));
            },
        });
    }
}

const AXPAuthGuard = (route, state) => {
    const sessionService = inject(AXPSessionService);
    return sessionService.isAuthenticatedWithLoading$.pipe(first(), map((value) => {
        if (value) {
            return true;
        }
        throw new AXPUnauthenticatedError(`Access denied. You are not currently logged in. Please log in to access this page. If you continue to see this message after logging in, please contact support.`, {
            redirectUrl: state.url,
        });
    }));
};

function initializeAppState(service) {
    return async () => {
        try {
            await service.restoreSession();
        }
        catch (error) {
            console.error(error);
        }
    };
}
class AXPAuthModule {
    static forRoot(configs) {
        return {
            ngModule: AXPAuthModule,
            providers: [
                ...(configs?.strategies || []),
                {
                    provide: 'AXPAuthModuleFactory',
                    useFactory: (registry) => () => {
                        registry.register(...(configs?.strategies || []));
                    },
                    deps: [AXPAuthStrategyRegistryService],
                    multi: true,
                },
            ],
        };
    }
    static forChild(configs) {
        return {
            ngModule: AXPAuthModule,
            providers: [
                ...(configs?.strategies || []),
                {
                    provide: 'AXPAuthModuleFactory',
                    useFactory: (registry) => () => {
                        registry.register(...(configs?.strategies || []));
                    },
                    deps: [AXPAuthStrategyRegistryService],
                    multi: true,
                },
            ],
        };
    }
    /**
     * @ignore
     */
    constructor(instances) {
        instances?.forEach((f) => {
            f();
        });
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPAuthModule, deps: [{ token: 'AXPAuthModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: AXPAuthModule, declarations: [AXPPermissionDirective, AXPFeatureDirective], exports: [AXPPermissionDirective, AXPFeatureDirective] }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPAuthModule, providers: [
            provideAppInitializer(() => {
                const initializerFn = initializeAppState(inject(AXPSessionService));
                return initializerFn();
            }),
            {
                provide: AXP_SESSION_SERVICE,
                useExisting: AXPSessionService,
            },
        ] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPAuthModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: [],
                    exports: [AXPPermissionDirective, AXPFeatureDirective],
                    declarations: [AXPPermissionDirective, AXPFeatureDirective],
                    providers: [
                        provideAppInitializer(() => {
                            const initializerFn = initializeAppState(inject(AXPSessionService));
                            return initializerFn();
                        }),
                        {
                            provide: AXP_SESSION_SERVICE,
                            useExisting: AXPSessionService,
                        },
                    ],
                }]
        }], ctorParameters: () => [{ type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: ['AXPAuthModuleFactory']
                }] }] });

class AXPAuthStrategy {
}

//#region ----   JWT Utility   ----
/**
 * Utility class for JWT token operations
 */
class JwtUtil {
    /**
     * Parses a JWT token and returns the payload
     */
    static parseJwt(token) {
        try {
            const base64Url = token.split('.')[1];
            const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
            const jsonPayload = decodeURIComponent(atob(base64)
                .split('')
                .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
                .join(''));
            return JSON.parse(jsonPayload);
        }
        catch (error) {
            throw new Error('Invalid JWT token');
        }
    }
}
//#endregion
//#region ----   PKCE Utility   ----
/**
 * Utility class for PKCE (Proof Key for Code Exchange) operations
 */
class PkceUtil {
    /**
     * Generates a random string for PKCE code verifier
     */
    static generateRandomString(length) {
        const array = new Uint8Array(length);
        crypto.getRandomValues(array);
        return this.base64UrlEncode(array);
    }
    /**
     * Generates PKCE code challenge from verifier
     */
    static async generateCodeChallenge(codeVerifier) {
        const encoder = new TextEncoder();
        const data = encoder.encode(codeVerifier);
        const digest = await crypto.subtle.digest('SHA-256', data);
        return this.base64UrlEncode(new Uint8Array(digest));
    }
    /**
     * Base64 URL encoding for PKCE
     */
    static base64UrlEncode(array) {
        return btoa(String.fromCharCode(...array))
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '');
    }
}
//#endregion
//#region ----   Time Utility   ----
/**
 * Utility class for time and date operations
 */
class TimeUtil {
    /**
     * Calculates the time difference in milliseconds between a future date and now
     */
    static expiresInMilliseconds(expiresInDate) {
        return new Date(expiresInDate).getTime() - new Date().getTime();
    }
    /**
     * Calculates expiration date from seconds
     */
    static calculateExpireInDate(expireInSeconds) {
        return new Date(Date.now() + expireInSeconds * 1000).toISOString();
    }
}
//#endregion

//#region ----   Challenge Data Types   ----
//#endregion

//#region ----   Abstract Challenge Provider   ----
/**
 * Abstract base class for login challenge providers
 *
 * Implement this class to create custom challenge mechanisms like:
 * - Image CAPTCHA
 * - reCAPTCHA
 * - SMS verification
 * - Email verification
 *
 * @example
 * ```typescript
 * @Injectable()
 * export class MyImageCaptchaProvider extends AXPLoginChallengeProvider {
 *   readonly name = 'image-captcha';
 *
 *   checkResponse(error: unknown): AXPChallengeCheckResult | null {
 *     if (error instanceof HttpErrorResponse) {
 *       if (error.error?.requiresCaptcha) {
 *         return { required: true };
 *       }
 *     }
 *     return null;
 *   }
 *
 *   async getChallenge(): Promise<AXPLoginChallengeData> {
 *     const response = await this.http.get('/api/captcha').toPromise();
 *     return {
 *       id: response.id,
 *       content: response.image,
 *       contentType: 'image-base64'
 *     };
 *   }
 *
 *   async refreshChallenge(): Promise<AXPLoginChallengeData> {
 *     return this.getChallenge();
 *   }
 *
 *   getChallengeComponent(): Type<AXPLoginChallengeComponentBase> {
 *     return MyCaptchaChallengeComponent;
 *   }
 * }
 * ```
 */
class AXPLoginChallengeProvider {
    /**
     * Returns the component type for rendering the challenge UI
     *
     * Override this method to provide a custom challenge UI component.
     * If not overridden (returns null), the login component will use
     * a default built-in UI.
     *
     * @returns Component type extending AXPLoginChallengeComponentBase, or null for default UI
     */
    getChallengeComponent() {
        return null;
    }
}
//#endregion

//#region ----   Injection Token   ----
/**
 * Injection token for the login challenge provider
 *
 * This token is optional - if not provided, no challenge mechanism will be used.
 *
 * @example
 * ```typescript
 * // In your app module or provider configuration:
 * providers: [
 *   {
 *     provide: AXP_LOGIN_CHALLENGE_PROVIDER,
 *     useClass: MyImageCaptchaProvider
 *   }
 * ]
 *
 * // In a component:
 * private challengeProvider = inject(AXP_LOGIN_CHALLENGE_PROVIDER, { optional: true });
 * ```
 */
const AXP_LOGIN_CHALLENGE_PROVIDER = new InjectionToken('AXP_LOGIN_CHALLENGE_PROVIDER');
//#endregion

//#region ----   Base Challenge Component   ----
/**
 * Base class for login challenge UI components
 *
 * Providers can extend this class to create custom challenge UIs.
 * The login component will render this component and listen to its outputs.
 *
 * @example
 * ```typescript
 * @Component({
 *   selector: 'my-captcha-challenge',
 *   template: `
 *     <div class="captcha-container">
 *       <img [src]="'data:image/png;base64,' + challengeData().content" />
 *       <input
 *         type="text"
 *         [value]="response()"
 *         (input)="onResponseChange($event)"
 *       />
 *       <button (click)="onRefreshClick()">Refresh</button>
 *     </div>
 *   `
 * })
 * export class MyCaptchaChallengeComponent extends AXPLoginChallengeComponentBase {
 *   response = signal('');
 *
 *   onResponseChange(event: Event) {
 *     const value = (event.target as HTMLInputElement).value;
 *     this.response.set(value);
 *     this.responseChange.emit(value);
 *   }
 *
 *   onRefreshClick() {
 *     this.refreshRequest.emit();
 *   }
 * }
 * ```
 */
class AXPLoginChallengeComponentBase {
    constructor() {
        //#region ----   Inputs   ----
        /**
         * Challenge data to display
         * Contains the image/content and metadata from the server
         */
        this.challengeData = input.required(...(ngDevMode ? [{ debugName: "challengeData" }] : []));
        /**
         * Whether the challenge is currently loading (e.g., refreshing)
         */
        this.isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
        //#endregion
        //#region ----   Outputs   ----
        /**
         * Emits when the user enters or changes their response
         * The login component will capture this value and include it in credentials
         */
        this.responseChange = output();
        /**
         * Emits when the user requests a new challenge (e.g., clicks refresh button)
         * The login component will call provider.refreshChallenge()
         */
        this.refreshRequest = output();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPLoginChallengeComponentBase, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: AXPLoginChallengeComponentBase, isStandalone: true, selector: "ng-component", inputs: { challengeData: { classPropertyName: "challengeData", publicName: "challengeData", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { responseChange: "responseChange", refreshRequest: "refreshRequest" }, ngImport: i0, template: '', isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPLoginChallengeComponentBase, decorators: [{
            type: Component,
            args: [{
                    template: '',
                    standalone: true,
                }]
        }], propDecorators: { challengeData: [{ type: i0.Input, args: [{ isSignal: true, alias: "challengeData", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], responseChange: [{ type: i0.Output, args: ["responseChange"] }], refreshRequest: [{ type: i0.Output, args: ["refreshRequest"] }] } });

/**
 * Generated bundle index. Do not edit.
 */

export { AXPAuthGuard, AXPAuthModule, AXPAuthStrategy, AXPAuthStrategyRegistryService, AXPFeatureDirective, AXPFeatureGuard, AXPLoginChallengeComponentBase, AXPLoginChallengeProvider, AXPPermissionDefinitionBuilder, AXPPermissionDefinitionGroupBuilder, AXPPermissionDefinitionProviderContext, AXPPermissionDefinitionService, AXPPermissionDirective, AXPPermissionEvaluatorScopeProvider, AXPPermissionGuard, AXPSessionContext, AXPSessionService, AXPSessionStatus, AXPUnauthenticatedError, AXPUnauthorizedError, AXP_APPLICATION_LOADER, AXP_FEATURE_CHECKER, AXP_FEATURE_LOADER, AXP_LOGIN_CHALLENGE_PROVIDER, AXP_PERMISSION_CHECKER, AXP_PERMISSION_DEFINITION_PROVIDER, AXP_PERMISSION_LOADER, AXP_TENANT_LOADER, JwtUtil, PkceUtil, TimeUtil, initializeAppState };
//# sourceMappingURL=acorex-platform-auth.mjs.map
