import * as i0 from '@angular/core';
import { InjectionToken, inject, Injectable, TemplateRef, ViewContainerRef, input, effect, Directive, Inject } from '@angular/core';
import { SdCacheService } from '@sdcorejs/angular/services/cache';
import { ArrayUtilities } from '@sdcorejs/angular/utilities/extensions';
import { resolveMaybeAsync } from '@sdcorejs/utils/models';

/**
 * InjectionToken cho cấu hình permission.
 *
 * Ví dụ provider:
 * {
 *   provide: SD_PERMISSION_CONFIGURATION,
 *   useValue: {
 *     disabled: false,
 *     loadPermissions: () => ['SAMPLE_C_EMPLOYEE_VIEW'],
 *     onForbiden: () => router.navigateByUrl('/layout/forbidden')
 *   }
 * }
 */
const SD_PERMISSION_CONFIGURATION = new InjectionToken('sd-permission.configuration');

class SdPermissionService {
    #permissionMapByKey = {};
    #configuration = inject(SD_PERMISSION_CONFIGURATION);
    #cacheService = inject(SdCacheService);
    #permissionsByKey = this.#cacheService.create('212a51fa-38d5-43b2-bd46-922d85950ba3', {
        type: 'session',
        default: {},
    });
    #loadedKeys = new Set();
    constructor() {
        this.#validateDuplicateConfigKeys();
    }
    #getConfigurations = () => {
        const config = this.#configuration;
        if (!config) {
            return [];
        }
        return Array.isArray(config) ? config : [config];
    };
    #normalizeKey = (key) => {
        return key === undefined ? '__undefined__' : key;
    };
    #validateDuplicateConfigKeys = () => {
        const seen = new Set();
        for (const config of this.#getConfigurations()) {
            const normalizedKey = this.#normalizeKey(config.key);
            if (seen.has(normalizedKey)) {
                const keyLabel = config.key === undefined ? 'undefined' : config.key;
                throw new Error(`[Permission] Duplicate permission configuration key: ${keyLabel}`);
            }
            seen.add(normalizedKey);
        }
    };
    #getConfigurationByKey = (key) => {
        return this.#getConfigurations().find(config => config.key === key);
    };
    #getEffectivePermissionKey = (key) => {
        if (this.#getConfigurationByKey(key)) {
            return key;
        }
        // Portal-level config uses key = undefined and acts as default fallback
        if (key !== undefined && this.#getConfigurationByKey(undefined)) {
            return undefined;
        }
        return key;
    };
    #setPermissionsForKey = (normalizedKey, permissions) => {
        const distinctPermissions = ArrayUtilities.distinct(permissions || []);
        const current = this.#permissionsByKey.get() || {};
        this.#permissionsByKey.set({
            ...current,
            [normalizedKey]: distinctPermissions,
        });
        const permissionMap = {};
        distinctPermissions.forEach(permission => {
            permissionMap[permission] = true;
        });
        this.#permissionMapByKey[normalizedKey] = permissionMap;
    };
    loadPermissions = async (key) => {
        const effectiveKey = this.#getEffectivePermissionKey(key);
        const normalizedKey = this.#normalizeKey(effectiveKey);
        if (this.#loadedKeys.has(normalizedKey)) {
            return this.#permissionsByKey.get()?.[normalizedKey] || [];
        }
        const configuration = this.#getConfigurationByKey(effectiveKey);
        if (!configuration) {
            this.#setPermissionsForKey(normalizedKey, []);
            this.#loadedKeys.add(normalizedKey);
            return [];
        }
        try {
            const permissions = await resolveMaybeAsync(configuration.loadPermissions());
            this.#setPermissionsForKey(normalizedKey, permissions || []);
        }
        catch (err) {
            console.error(err);
            this.#setPermissionsForKey(normalizedKey, []);
        }
        finally {
            this.#loadedKeys.add(normalizedKey);
        }
        return this.#permissionsByKey.get()?.[normalizedKey] || [];
    };
    loadAllPermissions = async () => {
        const configurations = this.#getConfigurations();
        if (!configurations.length) {
            await this.loadPermissions(undefined);
            return;
        }
        await Promise.all(configurations.map(config => this.loadPermissions(config.key)));
    };
    hasPermission = (permission, key) => {
        if (!permission?.toString()) {
            return true;
        }
        const effectiveKey = this.#getEffectivePermissionKey(key);
        const configuration = this.#getConfigurationByKey(effectiveKey);
        if (configuration?.disabled) {
            return true;
        }
        const normalizedKey = this.#normalizeKey(effectiveKey);
        const permissionMap = this.#permissionMapByKey[normalizedKey] || {};
        const permissions = Array.isArray(permission) ? permission : [permission];
        return permissions.some(val => permissionMap[val]);
    };
    getToken = async (key) => {
        const effectiveKey = this.#getEffectivePermissionKey(key);
        const getToken = this.#getConfigurationByKey(effectiveKey)?.getToken;
        if (!getToken) {
            throw new Error('[Permission] Method getToken');
        }
        const token = await resolveMaybeAsync(getToken());
        if (token === '') {
            return undefined;
        }
        return token;
    };
    decodeToken = async (key) => {
        const token = await this.getToken(key);
        if (!token) {
            return null;
        }
        try {
            const payload = token.split('.')[1];
            const base64 = payload.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) {
            console.error('Invalid token', error);
            return null;
        }
    };
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [] });

class SdPermissionDirective {
    #templateRef = inject(TemplateRef);
    #viewContainerRef = inject(ViewContainerRef);
    #permissionService = inject(SdPermissionService);
    // Nếu là mảng thì chỉ cần có 1 permission trong mảng đó xem như có quyền
    sdPermission = input(undefined, ...(ngDevMode ? [{ debugName: "sdPermission" }] : []));
    sdPermissionKey = input(undefined, ...(ngDevMode ? [{ debugName: "sdPermissionKey" }] : []));
    constructor() {
        effect(() => {
            const permission = this.sdPermission();
            const permissionKey = this.sdPermissionKey();
            this.#viewContainerRef.clear();
            // Nếu không gắn permission thì render
            if (!permission?.toString()) {
                this.#viewContainerRef.createEmbeddedView(this.#templateRef);
                return;
            }
            // Kiểm tra permission theo key (nếu có)
            if (this.#permissionService.hasPermission(permission, permissionKey)) {
                this.#viewContainerRef.createEmbeddedView(this.#templateRef);
            }
        });
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.24", type: SdPermissionDirective, isStandalone: true, selector: "[sdPermission]", inputs: { sdPermission: { classPropertyName: "sdPermission", publicName: "sdPermission", isSignal: true, isRequired: false, transformFunction: null }, sdPermissionKey: { classPropertyName: "sdPermissionKey", publicName: "sdPermissionKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[sdPermission]',
                }]
        }], ctorParameters: () => [], propDecorators: { sdPermission: [{ type: i0.Input, args: [{ isSignal: true, alias: "sdPermission", required: false }] }], sdPermissionKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "sdPermissionKey", required: false }] }] } });

class SdPermissionGuard {
    configuration;
    permissionService;
    constructor(configuration, permissionService) {
        this.configuration = configuration;
        this.permissionService = permissionService;
    }
    #getConfigurations = () => {
        const config = this.configuration;
        if (!config) {
            return [];
        }
        return Array.isArray(config) ? config : [config];
    };
    canActivate = async (next, state) => {
        // Guard ở layer portal: preload toàn bộ permission theo tất cả key đã cấu hình
        await this.permissionService.loadAllPermissions().catch(console.error);
        return true;
    };
    canActivateChild = async (activatedRoute, state) => {
        const permission = activatedRoute.data['permission'];
        const permissionKey = activatedRoute.data?.['permissionKey'];
        if (this.permissionService.hasPermission(permission, permissionKey)) {
            return true;
        }
        const configurations = this.#getConfigurations();
        const onForbiden = configurations
            .filter(config => config.key === permissionKey || (permissionKey !== undefined && config.key === undefined))
            .map(config => config.onForbiden)
            .find(val => !!val);
        onForbiden?.();
        return false;
    };
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionGuard, deps: [{ token: SD_PERMISSION_CONFIGURATION }, { token: SdPermissionService }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionGuard, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdPermissionGuard, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: undefined, decorators: [{
                    type: Inject,
                    args: [SD_PERMISSION_CONFIGURATION]
                }] }, { type: SdPermissionService }] });

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

export { SD_PERMISSION_CONFIGURATION, SdPermissionDirective, SdPermissionGuard, SdPermissionService };
//# sourceMappingURL=sdcorejs-angular-modules-permission.mjs.map
